[
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": ".cache\ndocs/dist\ndocs/search.json\ndocs/**/*.min.js\ndist\nexamples\nnode_modules\nsrc/react\nscripts\n\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "/* eslint-env node */\n\nmodule.exports = {\n  plugins: [\n    '@typescript-eslint',\n    'wc',\n    'lit',\n    'lit-a11y',\n    'chai-expect',\n    'chai-friendly',\n    'import',\n    'sort-imports-es6-autofix'\n  ],\n  extends: [\n    'eslint:recommended',\n    'plugin:wc/recommended',\n    'plugin:wc/best-practice',\n    'plugin:lit/recommended',\n    'plugin:lit-a11y/recommended'\n  ],\n  env: {\n    es2021: true,\n    browser: true\n  },\n  parserOptions: {\n    sourceType: 'module'\n  },\n  overrides: [\n    {\n      extends: [\n        'plugin:@typescript-eslint/eslint-recommended',\n        'plugin:@typescript-eslint/recommended',\n        'plugin:@typescript-eslint/recommended-requiring-type-checking'\n      ],\n      parser: '@typescript-eslint/parser',\n      parserOptions: {\n        sourceType: 'module',\n        project: './tsconfig.json',\n        tsconfigRootDir: __dirname\n      },\n      files: ['*.ts'],\n      rules: {\n        'default-param-last': 'off',\n        '@typescript-eslint/default-param-last': 'error',\n        'no-empty-function': 'off',\n        '@typescript-eslint/no-empty-function': 'warn',\n        'no-implied-eval': 'off',\n        '@typescript-eslint/no-implied-eval': 'error',\n        'no-invalid-this': 'off',\n        '@typescript-eslint/no-invalid-this': 'error',\n        'no-shadow': 'off',\n        '@typescript-eslint/no-shadow': 'error',\n        'no-throw-literal': 'off',\n        '@typescript-eslint/no-throw-literal': 'error',\n        'no-unused-expressions': 'off',\n        '@typescript-eslint/prefer-regexp-exec': 'off',\n        '@typescript-eslint/no-unused-expressions': 'error',\n        '@typescript-eslint/unbound-method': 'off',\n        '@typescript-eslint/no-non-null-assertion': 'off',\n        '@typescript-eslint/no-floating-promises': 'off',\n        '@typescript-eslint/no-misused-promises': [\n          'error',\n          {\n            checksVoidReturn: false\n          }\n        ],\n        '@typescript-eslint/consistent-type-assertions': [\n          'warn',\n          {\n            assertionStyle: 'as',\n            objectLiteralTypeAssertions: 'never'\n          }\n        ],\n        '@typescript-eslint/consistent-type-imports': 'warn',\n        '@typescript-eslint/no-base-to-string': 'error',\n        '@typescript-eslint/no-confusing-non-null-assertion': 'error',\n        '@typescript-eslint/no-invalid-void-type': 'error',\n        '@typescript-eslint/no-require-imports': 'error',\n        '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',\n        '@typescript-eslint/no-unnecessary-condition': 'off',\n        '@typescript-eslint/no-unnecessary-qualifier': 'warn',\n        '@typescript-eslint/no-unnecessary-type-assertion': 'off',\n        '@typescript-eslint/non-nullable-type-assertion-style': 'warn',\n        '@typescript-eslint/prefer-for-of': 'warn',\n        '@typescript-eslint/prefer-optional-chain': 'warn',\n        '@typescript-eslint/prefer-ts-expect-error': 'warn',\n        '@typescript-eslint/prefer-return-this-type': 'error',\n        '@typescript-eslint/prefer-string-starts-ends-with': 'warn',\n        '@typescript-eslint/require-array-sort-compare': 'error',\n        '@typescript-eslint/unified-signatures': 'warn',\n        '@typescript-eslint/array-type': 'warn',\n        '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],\n        '@typescript-eslint/member-delimiter-style': 'warn',\n        '@typescript-eslint/method-signature-style': 'warn',\n        '@typescript-eslint/no-extraneous-class': 'error',\n        '@typescript-eslint/no-redundant-type-constituents': 'off',\n        '@typescript-eslint/parameter-properties': 'error',\n        '@typescript-eslint/strict-boolean-expressions': 'off'\n      }\n    },\n    {\n      files: ['**/*.cjs'],\n      env: {\n        node: true\n      }\n    },\n    {\n      extends: ['plugin:chai-expect/recommended', 'plugin:chai-friendly/recommended'],\n      files: ['*.test.ts'],\n      rules: {\n        '@typescript-eslint/no-unsafe-call': 'off',\n        '@typescript-eslint/no-unused-expressions': 'off'\n      }\n    }\n  ],\n  rules: {\n    'no-template-curly-in-string': 'error',\n    'array-callback-return': 'error',\n    'comma-dangle': 'off',\n    'consistent-return': 'error',\n    curly: 'off',\n    'default-param-last': 'error',\n    eqeqeq: 'error',\n    'lit-a11y/click-events-have-key-events': 'off',\n    'no-constructor-return': 'error',\n    'no-empty-function': 'warn',\n    'no-eval': 'error',\n    'no-extend-native': 'error',\n    'no-extra-bind': 'error',\n    'no-floating-decimal': 'error',\n    'no-implicit-coercion': 'off',\n    'no-implicit-globals': 'error',\n    'no-implied-eval': 'error',\n    'no-invalid-this': 'error',\n    'no-labels': 'error',\n    'no-lone-blocks': 'error',\n    'no-new': 'error',\n    'no-new-func': 'error',\n    'no-new-wrappers': 'error',\n    'no-octal-escape': 'error',\n    'no-proto': 'error',\n    'no-return-assign': 'warn',\n    'no-script-url': 'error',\n    'no-self-compare': 'warn',\n    'no-sequences': 'warn',\n    'no-throw-literal': 'error',\n    'no-unmodified-loop-condition': 'error',\n    'no-unused-expressions': 'warn',\n    'no-useless-call': 'error',\n    'no-useless-concat': 'error',\n    'no-useless-return': 'warn',\n    'prefer-promise-reject-errors': 'error',\n    radix: 'off',\n    'require-await': 'error',\n    'wrap-iife': ['warn', 'inside'],\n    'no-shadow': 'error',\n    'no-array-constructor': 'error',\n    'no-bitwise': 'error',\n    'no-multi-assign': 'warn',\n    'no-new-object': 'error',\n    'no-useless-computed-key': 'warn',\n    'no-useless-rename': 'warn',\n    'no-var': 'error',\n    'prefer-const': 'warn',\n    'prefer-numeric-literals': 'warn',\n    'prefer-object-spread': 'warn',\n    'prefer-rest-params': 'warn',\n    'prefer-spread': 'warn',\n    'prefer-template': 'off',\n    'no-else-return': 'off',\n    'func-names': ['warn', 'never'],\n    'one-var': ['warn', 'never'],\n    'operator-assignment': 'warn',\n    'prefer-arrow-callback': 'warn',\n    'no-restricted-imports': [\n      'warn',\n      {\n        paths: [\n          {\n            name: '.',\n            message: 'Usage of local index imports is not allowed.'\n          },\n          {\n            name: './index',\n            message: 'Import from the source file instead.'\n          }\n        ]\n      }\n    ],\n    'import/extensions': [\n      'error',\n      'always',\n      {\n        ignorePackages: true,\n        pattern: {\n          js: 'always',\n          ts: 'never'\n        }\n      }\n    ],\n    'import/no-duplicates': 'warn',\n    'sort-imports-es6-autofix/sort-imports-es6': [\n      2,\n      {\n        ignoreCase: true,\n        ignoreMemberSort: false,\n        memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single']\n      }\n    ],\n    'wc/guard-super-call': 'off'\n  }\n};\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cory@abeautifulsite.net. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4.\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [claviska]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: Create a bug report to help us fix a demonstrable problem with code in the library.\ntitle: ''\nlabels: bug\nassignees: \n---\n\n### Describe the bug\nA bug is _a demonstrable problem_ caused by code in the library. Please provide a clear and concise description of what the bug is here.\n\n### To Reproduce\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '...'\n3. Scroll down to '...'\n4. See error\n\n### Demo\n\nIf the bug isn't obvious, please provide a link to a CodePen or Fiddle with a minimal reproduction. Bugs that have repros get attention faster than those that don't.\n\nTip: use the CodePen button on any example in the docs!\n\n### Screenshots\nIf applicable, add screenshots to help explain the bug.\n\n### Browser / OS\n - OS: [e.g. Mac, Windows]\n - Browser: [e.g. Chrome, Firefox, Safari]\n - Browser version: [e.g. 22]\n\n### Additional information\nProvide any additional information about the bug here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: Feature Requests\n    url: https://github.com/shoelace-style/shoelace/discussions/categories/ideas\n    about: All requests for new features should go here.\n  - name: Help & Support\n    url: https://github.com/shoelace-style/shoelace/discussions/categories/help\n    about: Please don't create issues for personal help requests. Instead, ask your question on the discussion forum.\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Reporting Security Issues\n\nWe take security issues in Shoelace very seriously and appreciate your efforts to disclose your findings responsibly.\n\nTo report a security issue, email [cory@abeautifulsite.net](mailto:cory@abeautifulsite.net) and include \"SHOELACE SECURITY\" in the subject line.\n\nWe'll respond as soon as possible and keep you updated throughout the process.\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n  push:\n    branches: [next]\n  pull_request:\n    branches: [next]\n\njobs:\n  build:\n    runs-on: ubuntu-22.04\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Update system packages\n        run: |\n          sudo apt-get update\n          sudo apt-get upgrade -y\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n      - run: npx playwright install --with-deps\n      - run: npm ci\n      - run: npm run verify\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# This workflow will create a GitHub release every time a tag is pushed\nname: Create GitHub Release\n\non:\n  push:\n    tags:\n      - \"v2.*\"\n      - \"v3.*\"\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"marvinpinto/action-automatic-releases@v1.2.1\"\n        with:\n          repo_token: \"${{ secrets.GITHUB_TOKEN }}\"\n          prerelease: false\n"
  },
  {
    "path": ".gitignore",
    "content": "_site\n.cache\n.DS_Store\ncdn\ndist\ndocs/assets/images/sprite.svg\nnode_modules\nsrc/react\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "tasks:\n  - init: npm install && npm run build\n    command: npm run start\n\nports:\n  - port: 3001\n    onOpen: ignore\n  - port: 4000-4999\n    onOpen: open-preview\n\ngithub:\n  prebuilds:\n    # enable for the master/default branch (defaults to true)\n    master: true\n    # enable for all branches in this repo (defaults to false)\n    branches: true\n    # enable for pull requests coming from this repo (defaults to true)\n    pullRequests: true\n    # enable for pull requests coming from forks (defaults to false)\n    pullRequestsFromForks: true\n    # add a check to pull requests (defaults to true)\n    addCheck: true\n    # add a \"Review in Gitpod\" button as a comment to pull requests (defaults to false)\n    addComment: false\n    # add a \"Review in Gitpod\" button to the pull request's description (defaults to false)\n    addBadge: true\n    # add a label once the prebuild is ready to pull requests (defaults to false)\n    addLabel: true\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx --no-install lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": "*.hbs\n.cache\n.github\ncspell.json\ndist\ndocs/search.json\nsrc/components/icon/icons\nsrc/react/index.ts\nnode_modules\npackage.json\npackage-lock.json\ntsconfig.json\ncdn\n_site\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\",\n    \"esbenp.prettier-vscode\",\n    \"bierner.lit-html\",\n    \"bashmish.es6-string-css\",\n    \"streetsidesoftware.code-spell-checker\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  }\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Shoelace\n\nBefore contributing, please review the contributions guidelines at:\n\n[shoelace.style/resources/contributing](https://shoelace.style/resources/contributing)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2020 A Beautiful Site, LLC\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# Shoelace is now Web Awesome 🧡!\n\n> [!IMPORTANT]  \n> **Shoelace is in maintenance mode (LTS)**. It is no longer actively being developed but remains available for use under the MIT license. Critical fixes may be released as needed; there is no fixed end date.\n> For active development and new features, check out Web Awesome at [https://webawesome.com](https://webawesome.com) and [https://github.com/shoelace-style/webawesome](https://github.com/shoelace-style/webawesome).\n\nWeb Awesome has an even larger library of free web [components](https://webawesome.com/docs/components/), plus [themes](https://webawesome.com/docs/themes/), [utilities](https://webawesome.com/docs/utilities/), [patterns](https://webawesome.com/docs/patterns/), and more.\n\n---\n\n# Shoelace\n\nA forward-thinking library of web components.\n\n- Works with all frameworks 🧩\n- Works with CDNs 🚛\n- Fully customizable with CSS 🎨\n- Includes an official dark theme 🌛\n- Built with accessibility in mind ♿️\n- Open source 😸\n\n---\n\n- Documentation: [shoelace.style](https://shoelace.style)\n- Shoelace Source (Maintenance Mode - LTS): [github.com/shoelace-style/shoelace](https://github.com/shoelace-style/shoelace)\n- Web Awesome Source (Active Development): [github.com/shoelace-style/webawesome](https://github.com/shoelace-style/webawesome)\n\n---\n\n## Shoemakers 🥾\n\nShoemakers, or \"Shoelace developers,\" can use this documentation to learn how to build Shoelace from source. You will need Node >= 14.17 to build and run the project locally.\n\n**You don't need to do any of this to use Shoelace!** This page is for people who want to contribute to the project, tinker with the source, or create a custom build of Shoelace.\n\nIf that's not what you're trying to do, the [documentation website](https://shoelace.style) is where you want to be.\n\n### What are you using to build Shoelace?\n\nComponents are built with [LitElement](https://lit-element.polymer-project.org/), a custom elements base class that provides an intuitive API and reactive data binding. The build is a custom script with bundling powered by [esbuild](https://esbuild.github.io/).\n\n### Forking the Repo\n\nStart by [forking the repo](https://github.com/shoelace-style/shoelace/fork) on GitHub, then clone it locally and install dependencies.\n\n```bash\ngit clone https://github.com/YOUR_GITHUB_USERNAME/shoelace\ncd shoelace\nnpm install\n```\n\n### Developing\n\nOnce you've cloned the repo, run the following command.\n\n```bash\nnpm start\n```\n\nThis will spin up the dev server. After the initial build, a browser will open automatically. There is currently no hot module reloading (HMR), as browsers don't provide a way to reregister custom elements, but most changes to the source will reload the browser automatically.\n\n### Building\n\nTo generate a production build, run the following command.\n\n```bash\nnpm run build\n```\n\n### Creating New Components\n\nTo scaffold a new component, run the following command, replacing `sl-tag-name` with the desired tag name.\n\n```bash\nnpm run create sl-tag-name\n```\n\nThis will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the \"Components\" section of the sidebar.\n\n### Contributing\n\nShoelace is open source under the MIT license. Bug fixes and maintenance updates may still be considered; for new features and active development, see [Web Awesome](https://webawesome.com). If you want to contribute here, please review the [contribution guidelines](CONTRIBUTING.md) first.\n\n## License\n\nShoelace is available under the terms of the MIT license.\n\nWhether you're building Shoelace or building something _with_ Shoelace — have fun creating! 🥾\n"
  },
  {
    "path": "cspell.json",
    "content": "{\n  \"version\": \"0.2\",\n  \"words\": [\n    \"activedescendant\",\n    \"allowfullscreen\",\n    \"animationend\",\n    \"Animista\",\n    \"apos\",\n    \"atrule\",\n    \"autocorrect\",\n    \"autofix\",\n    \"autoload\",\n    \"autoloader\",\n    \"autoloading\",\n    \"autoplay\",\n    \"bezier\",\n    \"Bokmål\",\n    \"boxicons\",\n    \"CACHEABLE\",\n    \"callout\",\n    \"callouts\",\n    \"cdndir\",\n    \"chatbubble\",\n    \"checkmark\",\n    \"claviska\",\n    \"Clippy\",\n    \"codebases\",\n    \"codepen\",\n    \"colocated\",\n    \"colour\",\n    \"combobox\",\n    \"Commonmark\",\n    \"Composability\",\n    \"Consolas\",\n    \"contenteditable\",\n    \"copydir\",\n    \"Cotte\",\n    \"coverpage\",\n    \"crossorigin\",\n    \"crutchcorn\",\n    \"csspart\",\n    \"cssproperty\",\n    \"datetime\",\n    \"describedby\",\n    \"Docsify\",\n    \"dogfood\",\n    \"dropdowns\",\n    \"easings\",\n    \"endraw\",\n    \"enterkeyhint\",\n    \"eqeqeq\",\n    \"erroneou\",\n    \"errormessage\",\n    \"esbuild\",\n    \"exportmaps\",\n    \"exportparts\",\n    \"fieldsets\",\n    \"formaction\",\n    \"formdata\",\n    \"formenctype\",\n    \"formmethod\",\n    \"formnovalidate\",\n    \"formtarget\",\n    \"FOUC\",\n    \"FOUCE\",\n    \"fullscreen\",\n    \"gestern\",\n    \"giga\",\n    \"globby\",\n    \"Grayscale\",\n    \"haspopup\",\n    \"heroicons\",\n    \"hexa\",\n    \"Iconoir\",\n    \"Iframes\",\n    \"iife\",\n    \"inputmode\",\n    \"ionicon\",\n    \"ionicons\",\n    \"jsDelivr\",\n    \"jsfiddle\",\n    \"keydown\",\n    \"keyframes\",\n    \"Kool\",\n    \"labelledby\",\n    \"Laravel\",\n    \"LaViska\",\n    \"linkify\",\n    \"listbox\",\n    \"listitem\",\n    \"litelement\",\n    \"lowercasing\",\n    \"Lucide\",\n    \"maxlength\",\n    \"Menlo\",\n    \"menuitemcheckbox\",\n    \"menuitemradio\",\n    \"middlewares\",\n    \"minlength\",\n    \"monospace\",\n    \"mousedown\",\n    \"mousemove\",\n    \"mouseout\",\n    \"mouseup\",\n    \"multiselectable\",\n    \"nextjs\",\n    \"nocheck\",\n    \"noopener\",\n    \"noreferrer\",\n    \"novalidate\",\n    \"npmdir\",\n    \"Numberish\",\n    \"onscrollend\",\n    \"outdir\",\n    \"ParamagicDev\",\n    \"peta\",\n    \"petabit\",\n    \"prismjs\",\n    \"progressbar\",\n    \"radiogroup\",\n    \"Railsbyte\",\n    \"remixicon\",\n    \"reregister\",\n    \"resizer\",\n    \"resizers\",\n    \"retargeted\",\n    \"RETRYABLE\",\n    \"rgba\",\n    \"roadmap\",\n    \"Roboto\",\n    \"roledescription\",\n    \"Sapan\",\n    \"saturationl\",\n    \"Schilp\",\n    \"scrollbars\",\n    \"scrollend\",\n    \"scroller\",\n    \"Segoe\",\n    \"semibold\",\n    \"sitedir\",\n    \"slotchange\",\n    \"smartquotes\",\n    \"spacebar\",\n    \"stylesheet\",\n    \"Tabbable\",\n    \"tabindex\",\n    \"tabler\",\n    \"tablist\",\n    \"tabpanel\",\n    \"templating\",\n    \"tera\",\n    \"testid\",\n    \"textareas\",\n    \"textfield\",\n    \"tinycolor\",\n    \"transitionend\",\n    \"treeitem\",\n    \"treeshaking\",\n    \"Triaging\",\n    \"turbolinks\",\n    \"typeof\",\n    \"unbundles\",\n    \"unbundling\",\n    \"unicons\",\n    \"unsanitized\",\n    \"unsupportive\",\n    \"valpha\",\n    \"valuenow\",\n    \"valuetext\",\n    \"vuejs\",\n    \"WEBP\",\n    \"Webpacker\",\n    \"wordmark\"\n  ],\n  \"ignorePaths\": [\n    \"package.json\",\n    \"package-lock.json\",\n    \"docs/assets/examples/include.html\",\n    \".vscode/**\",\n    \"src/translations/!(en).ts\",\n    \"**/*.min.js\"\n  ],\n  \"ignoreRegExpList\": [\n    \"(^|[^a-z])sl[a-z]*(^|[^a-z])\"\n  ],\n  \"useGitignore\": true\n}\n"
  },
  {
    "path": "custom-elements-manifest.config.js",
    "content": "import * as path from 'path';\nimport { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';\nimport { customElementVsCodePlugin } from 'custom-element-vs-code-integration';\nimport { customElementVuejsPlugin } from 'custom-element-vuejs-integration';\nimport { parse } from 'comment-parser';\nimport { pascalCase } from 'pascal-case';\nimport commandLineArgs from 'command-line-args';\nimport fs from 'fs';\n\nconst packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8'));\nconst { name, description, version, author, homepage, license } = packageData;\n\nconst { outdir } = commandLineArgs([\n  { name: 'litelement', type: String },\n  { name: 'analyze', defaultOption: true },\n  { name: 'outdir', type: String }\n]);\n\nfunction noDash(string) {\n  return string.replace(/^\\s?-/, '').trim();\n}\n\nfunction replace(string, terms) {\n  terms.forEach(({ from, to }) => {\n    string = string?.replace(from, to);\n  });\n\n  return string;\n}\n\nexport default {\n  globs: ['src/components/**/*.component.ts'],\n  exclude: ['**/*.styles.ts', '**/*.test.ts'],\n  plugins: [\n    // Append package data\n    {\n      name: 'shoelace-package-data',\n      packageLinkPhase({ customElementsManifest }) {\n        customElementsManifest.package = { name, description, version, author, homepage, license };\n      }\n    },\n\n    // Infer tag names because we no longer use @customElement decorators.\n    {\n      name: 'shoelace-infer-tag-names',\n      analyzePhase({ ts, node, moduleDoc }) {\n        switch (node.kind) {\n          case ts.SyntaxKind.ClassDeclaration: {\n            const className = node.name.getText();\n            const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);\n\n            const importPath = moduleDoc.path;\n\n            // This is kind of a best guess at components. \"thing.component.ts\"\n            if (!importPath.endsWith('.component.ts')) {\n              return;\n            }\n\n            const tagNameWithoutPrefix = path.basename(importPath, '.component.ts');\n            const tagName = 'sl-' + tagNameWithoutPrefix;\n\n            classDoc.tagNameWithoutPrefix = tagNameWithoutPrefix;\n            classDoc.tagName = tagName;\n\n            // This used to be set to true by @customElement\n            classDoc.customElement = true;\n          }\n        }\n      }\n    },\n\n    // Parse custom jsDoc tags\n    {\n      name: 'shoelace-custom-tags',\n      analyzePhase({ ts, node, moduleDoc }) {\n        switch (node.kind) {\n          case ts.SyntaxKind.ClassDeclaration: {\n            const className = node.name.getText();\n            const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);\n            const customTags = ['animation', 'dependency', 'documentation', 'since', 'status', 'title'];\n            let customComments = '/**';\n\n            node.jsDoc?.forEach(jsDoc => {\n              jsDoc?.tags?.forEach(tag => {\n                const tagName = tag.tagName.getText();\n\n                if (customTags.includes(tagName)) {\n                  customComments += `\\n * @${tagName} ${tag.comment}`;\n                }\n              });\n            });\n\n            // This is what allows us to map JSDOC comments to ReactWrappers.\n            classDoc['jsDoc'] = node.jsDoc?.map(jsDoc => jsDoc.getFullText()).join('\\n');\n\n            const parsed = parse(`${customComments}\\n */`);\n            parsed[0].tags?.forEach(t => {\n              switch (t.tag) {\n                // Animations\n                case 'animation':\n                  if (!Array.isArray(classDoc['animations'])) {\n                    classDoc['animations'] = [];\n                  }\n                  classDoc['animations'].push({\n                    name: t.name,\n                    description: noDash(t.description)\n                  });\n                  break;\n\n                // Dependencies\n                case 'dependency':\n                  if (!Array.isArray(classDoc['dependencies'])) {\n                    classDoc['dependencies'] = [];\n                  }\n                  classDoc['dependencies'].push(t.name);\n                  break;\n\n                // Value-only metadata tags\n                case 'documentation':\n                case 'since':\n                case 'status':\n                case 'title':\n                  classDoc[t.tag] = t.name;\n                  break;\n\n                // All other tags\n                default:\n                  if (!Array.isArray(classDoc[t.tag])) {\n                    classDoc[t.tag] = [];\n                  }\n\n                  classDoc[t.tag].push({\n                    name: t.name,\n                    description: t.description,\n                    type: t.type || undefined\n                  });\n              }\n            });\n          }\n        }\n      }\n    },\n\n    {\n      name: 'shoelace-react-event-names',\n      analyzePhase({ ts, node, moduleDoc }) {\n        switch (node.kind) {\n          case ts.SyntaxKind.ClassDeclaration: {\n            const className = node.name.getText();\n            const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);\n\n            if (classDoc?.events) {\n              classDoc.events.forEach(event => {\n                event.reactName = `on${pascalCase(event.name)}`;\n                event.eventName = `${pascalCase(event.name)}Event`;\n              });\n            }\n          }\n        }\n      }\n    },\n\n    {\n      name: 'shoelace-translate-module-paths',\n      packageLinkPhase({ customElementsManifest }) {\n        customElementsManifest?.modules?.forEach(mod => {\n          //\n          // CEM paths look like this:\n          //\n          //  src/components/button/button.ts\n          //\n          // But we want them to look like this:\n          //\n          //  components/button/button.js\n          //\n          const terms = [\n            { from: /^src\\//, to: '' }, // Strip the src/ prefix\n            { from: /\\.component.(t|j)sx?$/, to: '.js' } // Convert .ts to .js\n          ];\n\n          mod.path = replace(mod.path, terms);\n\n          for (const ex of mod.exports ?? []) {\n            ex.declaration.module = replace(ex.declaration.module, terms);\n          }\n\n          for (const dec of mod.declarations ?? []) {\n            if (dec.kind === 'class') {\n              for (const member of dec.members ?? []) {\n                if (member.inheritedFrom) {\n                  member.inheritedFrom.module = replace(member.inheritedFrom.module, terms);\n                }\n              }\n            }\n          }\n        });\n      }\n    },\n\n    // Generate custom VS Code data\n    customElementVsCodePlugin({\n      outdir,\n      cssFileName: null,\n      referencesTemplate: (_, tag) => [\n        {\n          name: 'Documentation',\n          url: `https://shoelace.style/components/${tag.replace('sl-', '')}`\n        }\n      ]\n    }),\n\n    customElementJetBrainsPlugin({\n      outdir: './dist',\n      excludeCss: true,\n      packageJson: false,\n      referencesTemplate: (_, tag) => {\n        return {\n          name: 'Documentation',\n          url: `https://shoelace.style/components/${tag.replace('sl-', '')}`\n        };\n      }\n    }),\n\n    customElementVuejsPlugin({\n      outdir: './dist/types/vue',\n      fileName: 'index.d.ts',\n      componentTypePath: (_, tag) => `../../components/${tag.replace('sl-', '')}/${tag.replace('sl-', '')}.component.js`\n    })\n  ]\n};\n"
  },
  {
    "path": "docs/_includes/component.njk",
    "content": "{% extends \"default.njk\" %}\n\n{# Find the component based on the `tag` front matter #}\n{% set component = getComponent('sl-' + page.fileSlug) %}\n\n{% block content %}\n  {# Determine the badge variant #}\n  {% if component.status == 'stable' %}\n    {% set badgeVariant = 'primary' %}\n  {% elseif component.status == 'experimental' %}\n    {% set badgeVariant = 'warning' %}\n  {% elseif component.status == 'planned' %}\n    {% set badgeVariant = 'neutral' %}\n  {% elseif component.status == 'deprecated' %}\n    {% set badgeVariant = 'danger' %}\n  {% else %}\n    {% set badgeVariant = 'neutral' %}\n  {% endif %}\n\n  {# Header #}\n  <header class=\"component-header\">\n    <h1>{{ component.name | classNameToComponentName }}</h1>\n\n    <div class=\"component-header__tag\">\n      <code>&lt;{{ component.tagName }}&gt; | {{ component.name }}</code>\n    </div>\n\n    <div class=\"component-header__info\">\n      <sl-badge variant=\"neutral\" pill>\n        Since {{component.since or '?' }}\n      </sl-badge>\n      <sl-badge variant=\"{{ badgeVariant }}\" pill style=\"text-transform: capitalize;\">\n        {{ component.status }}\n      </sl-badge>\n    </div>\n  </header>\n\n  <p class=\"component-summary\">\n    {% if component.summary %}\n      {{ component.summary | markdownInline | safe }}\n    {% endif %}\n  </p>\n\n  {# Markdown content #}\n  {{ content | safe }}\n\n  {# Importing #}\n  <h2>Importing</h2>\n  <p>\n    If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use\n    any of the following snippets to <a href=\"/getting-started/installation#cherry-picking\">cherry pick</a> this component.\n  </p>\n\n  <sl-tab-group>\n    <sl-tab slot=\"nav\" panel=\"script\">Script</sl-tab>\n    <sl-tab slot=\"nav\" panel=\"import\">Import</sl-tab>\n    <sl-tab slot=\"nav\" panel=\"bundler\">Bundler</sl-tab>\n    <sl-tab slot=\"nav\" panel=\"react\">React</sl-tab>\n\n    <sl-tab-panel name=\"script\">\n      <p>\n        To import this component from <a href=\"https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace\">the CDN</a>\n        using a script tag:\n      </p>\n      <pre><code class=\"language-html\">&lt;script type=&quot;module&quot; src=&quot;https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}&quot;&gt;&lt;/script&gt;</code></pre>\n    </sl-tab-panel>\n\n    <sl-tab-panel name=\"import\">\n      <p>\n        To import this component from <a href=\"https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace\">the CDN</a>\n        using a JavaScript import:\n      </p>\n      <pre><code class=\"language-js\">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}';</code></pre>\n    </sl-tab-panel>\n\n    <sl-tab-panel name=\"bundler\">\n      <p>\n        To import this component using <a href=\"{{ rootUrl('/getting-started/installation#bundling') }}\">a bundler</a>:\n      </p>\n      <pre><code class=\"language-js\">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>\n    </sl-tab-panel>\n\n    <sl-tab-panel name=\"react\">\n      <p>\n        To import this component as a <a href=\"/frameworks/react\">React component</a>:\n      </p>\n      <pre><code class=\"language-js\">import {{ component.name }} from '@shoelace-style/shoelace/{{ meta.npmdir }}/react/{{ component.tagNameWithoutPrefix }}';</code></pre>\n    </sl-tab-panel>\n  </sl-tab-group>\n\n  {# Slots #}\n  {% if component.slots.length %}\n    <h2>Slots</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for slot in component.slots %}\n          <tr>\n            <td class=\"nowrap\">\n              {% if slot.name %}\n                <code>{{ slot.name }}</code>\n              {% else %}\n                (default)\n              {% endif %}\n            </td>\n            <td>{{ slot.description | markdownInline | safe }}</td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/usage#slots') }}\">using slots</a>.</em></p>\n  {% endif %}\n\n  {# Properties #}\n  {% if component.properties.length %}\n    <h2>Properties</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n          <th class=\"table-reflects\">Reflects</th>\n          <th class=\"table-type\">Type</th>\n          <th class=\"table-default\">Default</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for prop in component.properties %}\n          <tr>\n            <td>\n              <code class=\"nowrap\">{{ prop.name }}</code>\n              {% if prop.attribute | length > 0 %}\n                {% if prop.attribute != prop.name %}\n                  <br>\n                  <sl-tooltip content=\"This attribute is different from its property\">\n                    <small>\n                      <code class=\"nowrap\">\n                        {{ prop.attribute }}\n                      </code>\n                    </small>\n                  </sl-tooltip>\n                {% endif %}\n              {% endif %}\n            </td>\n            <td>\n              {{ prop.description | markdownInline | safe }}\n            </td>\n            <td style=\"text-align: center;\">\n              {% if prop.reflects %}\n                <sl-icon label=\"yes\" name=\"check-lg\"></sl-icon>\n              {% endif %}\n            </td>\n            <td>\n              {% if prop.type.text %}\n                <code>{{ prop.type.text | trimPipes | markdownInline | safe }}</code>\n              {% else %}\n                -\n              {% endif %}\n            </td>\n            <td>\n              {% if prop.default %}\n                <code>{{ prop.default | markdownInline | safe }}</code>\n              {% else %}\n                -\n              {% endif %}\n            </td>\n          </tr>\n        {% endfor %}\n        <tr>\n          <td class=\"nowrap\"><code>updateComplete</code></td>\n          <td>\n            A read-only promise that resolves when the component has\n            <a href=\"/getting-started/usage?#component-rendering-and-updating\">finished updating</a>.\n          </td>\n          <td></td>\n          <td></td>\n          <td></td>\n        </tr>\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/usage#attributes-and-properties') }}\">attributes and properties</a>.</em></p>\n  {% endif %}\n\n  {# Events #}\n  {% if component.events.length %}\n    <h2>Events</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\" data-flavor=\"html\">Name</th>\n          <th class=\"table-name\" data-flavor=\"react\">React Event</th>\n          <th class=\"table-description\">Description</th>\n          <th class=\"table-event-detail\">Event Detail</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for event in component.events %}\n          <tr>\n            <td data-flavor=\"html\"><code class=\"nowrap\">{{ event.name }}</code></td>\n            <td data-flavor=\"react\"><code class=\"nowrap\">{{ event.reactName }}</code></td>\n            <td>{{ event.description | markdownInline | safe }}</td>\n            <td>\n              {% if event.type.text %}\n                <code>{{ event.type.text | trimPipes  }}</code>\n              {% else %}\n                -\n              {% endif %}\n            </td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/usage#events') }}\">events</a>.</em></p>\n  {% endif %}\n\n  {# Methods #}\n  {% if component.methods.length %}\n    <h2>Methods</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n          <th class=\"table-arguments\">Arguments</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for method in component.methods %}\n          <tr>\n            <td class=\"nowrap\"><code>{{ method.name }}()</code></td>\n            <td>{{ method.description | markdownInline | safe }}</td>\n            <td>\n              {% if method.parameters.length %}\n                <code>\n                  {% for param in method.parameters %}\n                    {{ param.name }}: {{ param.type.text | trimPipes }}{% if not loop.last %},{% endif %}\n                  {% endfor %}\n                </code>\n              {% else %}\n                -\n              {% endif %}\n            </td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/usage#methods') }}\">methods</a>.</em></p>\n  {% endif %}\n\n  {# Custom Properties #}\n  {% if component.cssProperties.length %}\n    <h2>Custom Properties</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n          <th class=\"table-default\">Default</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for cssProperty in component.cssProperties %}\n          <tr>\n            <td class=\"nowrap\"><code>{{ cssProperty.name }}</code></td>\n            <td>{{ cssProperty.description | markdownInline | safe }}</td>\n            <td>{{ cssProperty.default }}</td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/customizing#custom-properties') }}\">customizing CSS custom properties</a>.</em></p>\n  {% endif %}\n\n  {# CSS Parts #}\n  {% if component.cssParts.length %}\n    <h2>Parts</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for cssPart in component.cssParts %}\n          <tr>\n            <td class=\"nowrap\"><code>{{ cssPart.name }}</code></td>\n            <td>{{ cssPart.description | markdownInline | safe }}</td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/customizing/#css-parts') }}\">customizing CSS parts</a>.</em></p>\n  {% endif %}\n\n  {# Animations #}\n  {% if component.animations.length %}\n    <h2>Animations</h2>\n\n    <table>\n      <thead>\n        <tr>\n          <th class=\"table-name\">Name</th>\n          <th class=\"table-description\">Description</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for animation in component.animations %}\n          <tr>\n            <td class=\"nowrap\"><code>{{ animation.name }}</code></td>\n            <td>{{ animation.description | markdownInline | safe }}</td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n\n    <p><em>Learn more about <a href=\"{{ rootUrl('/getting-started/customizing#animations') }}\">customizing animations</a>.</em></p>\n  {% endif %}\n\n  {# Dependencies #}\n  {% if component.dependencies.length %}\n    <h2>Dependencies</h2>\n\n    <p>This component automatically imports the following dependencies.</p>\n\n    <ul>\n      {% for dependency in component.dependencies %}\n        <li><code>&lt;{{ dependency }}&gt;</code></li>\n      {% endfor %}\n    </ul>\n  {% endif %}\n{% endblock %}\n"
  },
  {
    "path": "docs/_includes/default.njk",
    "content": "<!DOCTYPE html>\n<html\n  lang=\"en\"\n  data-layout=\"{{ layout }}\"\n  data-shoelace-version=\"{{ meta.version }}\"\n>\n  <head>\n    {# Metadata #}\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"description\" content=\"{{ meta.description }}\" />\n    <title>{{ meta.title }}</title>\n\n    {# Opt out of Turbo caching #}\n    <meta name=\"turbo-cache-control\">\n\n    {# Stylesheets #}\n    <link rel=\"stylesheet\" href=\"{{ assetUrl('styles/docs.css') }}\" />\n    <link rel=\"stylesheet\" href=\"{{ assetUrl('styles/code-previews.css') }}\" />\n    <link rel=\"stylesheet\" href=\"{{ assetUrl('styles/search.css') }}\" />\n\n    {# Favicons #}\n    <link rel=\"icon\" href=\"{{ assetUrl('images/logo.svg') }}\" type=\"image/x-icon\" />\n\n    {# Twitter Cards #}\n    <meta name=\"twitter:card\" content=\"summary\" />\n    <meta name=\"twitter:creator\" content=\"shoelace_style\" />\n    <meta name=\"twitter:image\" content=\"{{ assetUrl(meta.image, true) }}\" />\n\n    {# OpenGraph #}\n    <meta property=\"og:url\" content=\"{{ rootUrl(page.url, true) }}\" />\n    <meta property=\"og:title\" content=\"{{ meta.title }}\" />\n    <meta property=\"og:description\" content=\"{{ meta.description }}\" />\n    <meta property=\"og:image\" content=\"{{ assetUrl(meta.image, true) }}\" />\n\n    {# Shoelace #}\n    <link rel=\"stylesheet\" href=\"/dist/themes/light.css\" />\n    <link rel=\"stylesheet\" href=\"/dist/themes/dark.css\" />\n    <script type=\"module\" src=\"/dist/shoelace-autoloader.js\"></script>\n\n    {# Set the initial theme and menu states here to prevent flashing #}\n    <script>\n      (() => {\n        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n        const theme = localStorage.getItem('theme') || 'auto';\n        document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));\n      })();\n    </script>\n\n    {# Turbo + Scroll positioning #}\n    <script src=\"{{ assetUrl('scripts/turbo.js') }}\" type=\"module\"></script>\n    <script src=\"{{ assetUrl('scripts/docs.js') }}\" defer></script>\n    <script src=\"{{ assetUrl('scripts/code-previews.js') }}\" defer></script>\n    <script src=\"{{ assetUrl('scripts/lunr.js') }}\" defer></script>\n    <script src=\"{{ assetUrl('scripts/search.js') }}\" defer></script>\n  </head>\n  <body>\n    <a id=\"skip-to-main\" class=\"visually-hidden\" href=\"#main-content\" data-smooth-link=\"false\">\n      Skip to main content\n    </a>\n\n    {# Menu toggle #}\n    <button id=\"menu-toggle\" type=\"button\" aria-label=\"Menu\">\n      <svg width=\"148\" height=\"148\" viewBox=\"0 0 148 148\" xmlns=\"http://www.w3.org/2000/svg\">\n        <g stroke=\"currentColor\" stroke-width=\"18\" fill=\"none\" fill-rule=\"evenodd\" stroke-linecap=\"round\">\n          <path d=\"M9.5 125.5h129M9.5 74.5h129M9.5 23.5h129\"></path>\n        </g>\n      </svg>\n    </button>\n\n    {# Icon toolbar #}\n    <div id=\"icon-toolbar\">\n      {# GitHub #}\n      <a href=\"https://github.com/shoelace-style/shoelace\" title=\"View Shoelace on GitHub\">\n        <sl-icon name=\"github\"></sl-icon>\n      </a>\n\n      {# Twitter #}\n      <a href=\"https://twitter.com/shoelace_style\" title=\"Follow Shoelace on Twitter\">\n        <sl-icon name=\"twitter\"></sl-icon>\n      </a>\n\n      {# Theme selector #}\n      <sl-dropdown id=\"theme-selector\" placement=\"bottom-end\" distance=\"3\">\n        <sl-button slot=\"trigger\" size=\"small\" variant=\"text\" caret title=\"Press \\ to toggle\">\n          <sl-icon class=\"only-light\" name=\"sun-fill\"></sl-icon>\n          <sl-icon class=\"only-dark\" name=\"moon-fill\"></sl-icon>\n        </sl-button>\n        <sl-menu>\n          <sl-menu-item type=\"checkbox\" value=\"light\">Light</sl-menu-item>\n          <sl-menu-item type=\"checkbox\" value=\"dark\">Dark</sl-menu-item>\n          <sl-divider></sl-divider>\n          <sl-menu-item type=\"checkbox\" value=\"auto\">System</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    </div>\n\n    <sl-dialog id=\"wa-dialog\" label=\"Shoelace is now Web Awesome!\">\n      <div class=\"dialog-billboard\">\n        <div class=\"dialog-billboard-content\">\n          {% include 'wa-logo-icon.njk' %}\n          <h2>Shoelace is now Web Awesome!</h2>\n        </div>\n      </div>\n\n      <div class=\"dialog-body\">\n        <p>Web Awesome has an even bigger library of free <a href=\"https://webawesome.com/docs/components/?utm_source=shoelace-docs&utm_medium=web&utm_campaign=wa-dialog\" target=\"_blank\">web components</a>. Plus <a href=\"https://webawesome.com/docs/themes/?utm_source=shoelace-docs&utm_medium=web&utm_campaign=wa-dialog\" target=\"_blank\">themes</a>, <a href=\"https://webawesome.com/docs/utilities/?utm_source=shoelace-docs&utm_medium=web&utm_campaign=wa-dialog\" target=\"_blank\">utilities</a>, <a href=\"https://webawesome.com/docs/patterns/?utm_source=shoelace-docs&utm_medium=web&utm_campaign=wa-dialog\" target=\"_blank\">patterns</a>, and more!</p>\n\n        <p><strong>Still open source. Now with more awesome.</strong></p>\n\n        <sl-divider></sl-divider>\n\n        <div class=\"dialog-actions\">\n          <sl-button variant=\"text\" class=\"close-wa-dialog\">Okay, got it</sl-button>\n          <div class=\"wa-ctas\">\n            <sl-button variant=\"primary\" size=\"large\" class=\"wa-pro-cta\" href=\"https://webawesome.com/docs?utm_source=shoelace-docs&utm_medium=web&utm_campaign=wa-dialog\" target=\"_blank\">\n              See What's New\n              <sl-icon slot=\"suffix\" name=\"arrow-right\"></sl-icon>\n            </sl-button>\n          </div>\n        </div>\n      </div>\n\n      <div slot=\"footer\">\n        <div class=\"sl-status\">\n          <strong>FYI, Shoelace is no longer actively being developed</strong>\n          <p>But it is still available for use and may receive updates as needed.</p>\n        </div>\n      </div>\n    </sl-dialog>\n    <script>\n      if (!sessionStorage.getItem('wa-dialog-dismissed')) {\n        customElements.whenDefined('sl-dialog').then(() => {\n          const dialog = document.getElementById('wa-dialog');\n          const closeButton = dialog.querySelector('sl-button.close-wa-dialog');\n\n          closeButton.addEventListener('click', () => dialog.hide());\n          dialog.addEventListener('sl-after-hide', () => {\n            sessionStorage.setItem('wa-dialog-dismissed', 'true');\n          }, { once: true });\n\n          dialog.show();\n        });\n      }\n    </script>\n\n    <aside id=\"sidebar\" data-preserve-scroll>\n      <header>\n        <a href=\"/\">\n          <img src=\"{{ assetUrl('images/wordmark.svg') }}\" alt=\"Shoelace\" />\n        </a>\n        <div class=\"sidebar-version\">\n          {{ meta.version }}\n        </div>\n      </header>\n\n      <div class=\"sidebar-buttons\">\n        <sl-button size=\"small\" class=\"repo-button repo-button--github\" href=\"https://github.com/shoelace-style/shoelace\" target=\"_blank\">\n          <sl-icon slot=\"prefix\" name=\"github\"></sl-icon> Code\n        </sl-button>\n        <sl-button size=\"small\" class=\"repo-button repo-button--star\" href=\"https://github.com/shoelace-style/shoelace/stargazers\" target=\"_blank\">\n          <sl-icon slot=\"prefix\" name=\"star-fill\"></sl-icon> Star\n        </sl-button>\n        <sl-button size=\"small\" class=\"repo-button repo-button--twitter\" href=\"https://twitter.com/shoelace_style\" target=\"_blank\">\n          <sl-icon slot=\"prefix\" name=\"twitter\"></sl-icon> Follow\n        </sl-button>\n      </div>\n\n      <button class=\"search-box\" type=\"button\" title=\"Press / to search\" aria-label=\"Search\" data-plugin=\"search\">\n        <sl-icon name=\"search\"></sl-icon>\n        <span>Search</span>\n      </button>\n\n      <nav>\n        {% include 'sidebar.njk' %}\n      </nav>\n    </aside>\n\n    {# Content #}\n    <main>\n      <a id=\"main-content\"></a>\n      <article id=\"content\" class=\"content{% if toc %} content--with-toc{% endif %}\">\n        {% if toc %}\n          <div class=\"content__toc\">\n            <ul>\n              <li class=\"top\"><a href=\"#\">{{ meta.title }}</a></li>\n            </ul>\n          </div>\n        {% endif %}\n\n        <div class=\"content__body\">\n          {% block content %}\n            {{ content | safe }}\n          {% endblock %}\n        </div>\n      </article>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/_includes/sidebar.njk",
    "content": "<ul>\n  <li>\n    <h2>Getting Started</h2>\n    <ul>\n      <li><a href=\"/\">Home</a></li>\n      <li><a href=\"/getting-started/installation\">Installation</a></li>\n      <li><a href=\"/getting-started/usage\">Usage</a></li>\n      <li><a href=\"/getting-started/themes\">Themes</a></li>\n      <li><a href=\"/getting-started/customizing\">Customizing</a></li>\n      <li><a href=\"/getting-started/form-controls\">Form Controls</a></li>\n      <li><a href=\"/getting-started/localization\">Localization</a></li>\n    </ul>\n  </li>\n  <li>\n    <h2>Frameworks</h2>\n    <ul>\n      <li><a href=\"/frameworks/react\">React</a></li>\n      <li><a href=\"/frameworks/vue\">Vue</a></li>\n      <li><a href=\"/frameworks/angular\">Angular</a></li>\n      <li><a href=\"/frameworks/svelte\">Svelte</a></li>\n    </ul>\n  </li>\n  <li>\n    <h2>Resources</h2>\n    <ul>\n      <li><a href=\"/resources/community\">Community</a></li>\n      <li><a href=\"https://github.com/shoelace-style/shoelace/discussions\">Help &amp; Support</a></li>\n      <li><a href=\"/resources/accessibility\">Accessibility</a></li>\n      <li><a href=\"/resources/contributing\">Contributing</a></li>\n      <li><a href=\"/resources/changelog\">Changelog</a></li>\n    </ul>\n  </li>\n  <li>\n    <h2>Components</h2>\n    <ul>\n      {% for component in meta.components %}\n        <li>\n          <a href=\"/components/{{ component.tagName | removeSlPrefix }}\">\n            {{ component.name | classNameToComponentName }}\n          </a>\n        </li>\n      {% endfor %}\n    </ul>\n  </li>\n  <li>\n    <h2>Design Tokens</h2>\n    <ul>\n      <li><a href=\"/tokens/typography\">Typography</a></li>\n      <li><a href=\"/tokens/color\">Color</a></li>\n      <li><a href=\"/tokens/spacing\">Spacing</a></li>\n      <li><a href=\"/tokens/elevation\">Elevation</a></li>\n      <li><a href=\"/tokens/border-radius\">Border Radius</a></li>\n      <li><a href=\"/tokens/transition\">Transition</a></li>\n      <li><a href=\"/tokens/z-index\">Z-index</a></li>\n      <li><a href=\"/tokens/more\">More Tokens</a></li>\n    </ul>\n  </li>\n  <li>\n    <h2>Tutorials</h2>\n    <ul>\n      <li><a href=\"/tutorials/integrating-with-astro\">Integrating with Astro</a></li>\n      <li><a href=\"/tutorials/integrating-with-laravel\">Integrating with Laravel</a></li>\n      <li><a href=\"/tutorials/integrating-with-nextjs\">Integrating with NextJS</a></li>\n      <li><a href=\"/tutorials/integrating-with-rails\">Integrating with Rails</a></li>\n    </ul>\n  </li>\n</ul>\n"
  },
  {
    "path": "docs/_includes/wa-logo-icon.njk",
    "content": "<svg class=\"wa-logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\" aria-hidden=\"true\"><path fill=\"currentColor\" d=\"M372.2 116C372.2 136.9 359.8 155 342 163.2L448 256L552.4 235.1C547.1 227.4 544 218 544 208C544 181.5 565.5 160 592 160C618.5 160 640 181.5 640 208C640 234 619.4 255.1 593.6 256L481 506.3C470.7 529.3 447.8 544 422.6 544L217.4 544C192.2 544 169.4 529.2 159 506.3L46.4 256C20.6 255.1 0 234 0 208C0 181.5 21.5 160 48 160C74.5 160 96 181.5 96 208C96 218.1 92.9 227.4 87.6 235.1L192 256L298.1 163.1C280.4 154.8 268.1 136.8 268.1 116C268.1 87.3 291.4 64 320.1 64C348.8 64 372.1 87.3 372.1 116L372.2 116z\"/></svg>\n"
  },
  {
    "path": "docs/_utilities/active-links.cjs",
    "content": "function normalizePathname(pathname) {\n  // Remove /index.html\n  if (pathname.endsWith('/index.html')) {\n    pathname = pathname.replace(/\\/index\\.html/, '');\n  }\n\n  // Remove trailing slashes\n  return pathname.replace(/\\/$/, '');\n}\n\n/**\n * Adds a class name to links that are currently active.\n */\nmodule.exports = function (doc, options) {\n  options = {\n    className: 'active-link', // the class to add to active links\n    pathname: undefined, // the current pathname to compare\n    within: 'body', // element containing the target links\n    ...options\n  };\n\n  const within = doc.querySelector(options.within);\n\n  if (!within) {\n    return doc;\n  }\n\n  within.querySelectorAll('a').forEach(link => {\n    if (normalizePathname(options.pathname) === normalizePathname(link.pathname)) {\n      link.classList.add(options.className);\n    }\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/anchor-headings.cjs",
    "content": "const { createSlug } = require('./strings.cjs');\n\n/**\n * Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.\n * The same document will be returned with the appropriate DOM manipulations.\n */\nmodule.exports = function (doc, options) {\n  options = {\n    levels: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], // the headings to convert\n    className: 'anchor-heading', // the class name to add\n    within: 'body', // the element containing the target headings\n    ...options\n  };\n\n  const within = doc.querySelector(options.within);\n\n  if (!within) {\n    return doc;\n  }\n\n  within.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {\n    const hasAnchor = heading.querySelector('a');\n    const anchor = doc.createElement('a');\n    let id = heading.textContent ?? '';\n    let suffix = 0;\n\n    // Skip heading levels we don't care about\n    if (!options.levels?.includes(heading.tagName.toLowerCase())) {\n      return;\n    }\n\n    // Convert dots to underscores\n    id = id.replace(/\\./g, '_');\n\n    // Turn it into a slug\n    id = createSlug(id);\n\n    // Make sure it starts with a letter\n    if (!/^[a-z]/i.test(id)) {\n      id = `id_${id}`;\n    }\n\n    // Make sure the id is unique\n    const originalId = id;\n    while (doc.getElementById(id) !== null) {\n      id = `${originalId}-${++suffix}`;\n    }\n\n    if (hasAnchor || !id) return;\n\n    heading.setAttribute('id', id);\n    anchor.setAttribute('href', `#${encodeURIComponent(id)}`);\n    anchor.setAttribute('aria-label', `Direct link to \"${heading.textContent}\"`);\n\n    if (options.className) {\n      heading.classList.add(options.className);\n    }\n\n    // Append the anchor\n    heading.append(anchor);\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/cem.cjs",
    "content": "const customElementsManifest = require('../../dist/custom-elements.json');\n\n//\n// Export it here so we can import it elsewhere and use the same version\n//\nmodule.exports.customElementsManifest = customElementsManifest;\n\n//\n// Gets all components from custom-elements.json and returns them in a more documentation-friendly format.\n//\nmodule.exports.getAllComponents = function () {\n  const allComponents = [];\n\n  customElementsManifest.modules?.forEach(module => {\n    module.declarations?.forEach(declaration => {\n      if (declaration.customElement) {\n        // Generate the dist path based on the src path and attach it to the component\n        declaration.path = module.path.replace(/^src\\//, 'dist/').replace(/\\.ts$/, '.js');\n\n        // Remove members that are private or don't have a description\n        const members = declaration.members?.filter(member => member.description && member.privacy !== 'private');\n        const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');\n        const properties = members?.filter(prop => {\n          // Look for a corresponding attribute\n          const attribute = declaration.attributes?.find(attr => attr.fieldName === prop.name);\n          if (attribute) {\n            prop.attribute = attribute.name || attribute.fieldName;\n          }\n\n          return prop.kind === 'field' && prop.privacy !== 'private';\n        });\n        allComponents.push({\n          ...declaration,\n          methods,\n          properties\n        });\n      }\n    });\n  });\n\n  // Build dependency graphs\n  allComponents.forEach(component => {\n    const dependencies = [];\n\n    // Recursively fetch sub-dependencies\n    function getDependencies(tag) {\n      const cmp = allComponents.find(c => c.tagName === tag);\n      if (!cmp || !Array.isArray(component.dependencies)) {\n        return;\n      }\n\n      cmp.dependencies?.forEach(dependentTag => {\n        if (!dependencies.includes(dependentTag)) {\n          dependencies.push(dependentTag);\n        }\n        getDependencies(dependentTag);\n      });\n    }\n\n    getDependencies(component.tagName);\n\n    component.dependencies = dependencies.sort();\n  });\n\n  // Sort by name\n  return allComponents.sort((a, b) => {\n    if (a.name < b.name) return -1;\n    if (a.name > b.name) return 1;\n    return 0;\n  });\n};\n"
  },
  {
    "path": "docs/_utilities/code-previews.cjs",
    "content": "let count = 1;\n\nfunction escapeHtml(str) {\n  return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\n/**\n * Turns code fields with the :preview suffix into interactive code previews.\n */\nmodule.exports = function (doc, options) {\n  options = {\n    within: 'body', // the element containing the code fields to convert\n    ...options\n  };\n\n  const within = doc.querySelector(options.within);\n  if (!within) {\n    return doc;\n  }\n\n  within.querySelectorAll('[class*=\":preview\"]').forEach(code => {\n    const pre = code.closest('pre');\n    if (!pre) {\n      return;\n    }\n    const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null;\n    const reactCode = adjacentPre?.querySelector('code[class$=\"react\"]');\n    const sourceGroupId = `code-preview-source-group-${count}`;\n    const isExpanded = code.getAttribute('class').includes(':expanded');\n    const noCodePen = code.getAttribute('class').includes(':no-codepen');\n\n    count++;\n\n    const htmlButton = `\n      <button type=\"button\"\n        title=\"Show HTML code\"\n        class=\"code-preview__button code-preview__button--html\"\n      >\n        HTML\n      </button>\n    `;\n\n    const reactButton = `\n      <button type=\"button\" title=\"Show React code\" class=\"code-preview__button code-preview__button--react\">\n        React\n      </button>\n    `;\n\n    const codePenButton = `\n      <button type=\"button\" class=\"code-preview__button code-preview__button--codepen\" title=\"Edit on CodePen\">\n        <svg\n          width=\"138\"\n          height=\"26\"\n          viewBox=\"0 0 138 26\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2.3\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n        >\n          <path d=\"M80 6h-9v14h9 M114 6h-9 v14h9 M111 13h-6 M77 13h-6 M122 20V6l11 14V6 M22 16.7L33 24l11-7.3V9.3L33 2L22 9.3V16.7z M44 16.7L33 9.3l-11 7.4 M22 9.3l11 7.3 l11-7.3 M33 2v7.3 M33 16.7V24 M88 14h6c2.2 0 4-1.8 4-4s-1.8-4-4-4h-6v14 M15 8c-1.3-1.3-3-2-5-2c-4 0-7 3-7 7s3 7 7 7 c2 0 3.7-0.8 5-2 M64 13c0 4-3 7-7 7h-5V6h5C61 6 64 9 64 13z\" />\n        </svg>\n      </button>\n    `;\n\n    const codePreview = `\n      <div class=\"code-preview ${isExpanded ? 'code-preview--expanded' : ''}\">\n        <div class=\"code-preview__preview\">\n          ${code.textContent}\n          <div class=\"code-preview__resizer\">\n            <sl-icon name=\"grip-vertical\"></sl-icon>\n          </div>\n        </div>\n\n        <div class=\"code-preview__source-group\" id=\"${sourceGroupId}\">\n          <div class=\"code-preview__source code-preview__source--html\" ${reactCode ? 'data-flavor=\"html\"' : ''}>\n            <pre><code class=\"language-html\">${escapeHtml(code.textContent)}</code></pre>\n          </div>\n\n          ${\n            reactCode\n              ? `\n            <div class=\"code-preview__source code-preview__source--react\" data-flavor=\"react\">\n              <pre><code class=\"language-jsx\">${escapeHtml(reactCode.textContent)}</code></pre>\n            </div>\n          `\n              : ''\n          }\n        </div>\n\n        <div class=\"code-preview__buttons\">\n          <button\n            type=\"button\"\n            class=\"code-preview__button code-preview__toggle\"\n            aria-expanded=\"${isExpanded ? 'true' : 'false'}\"\n            aria-controls=\"${sourceGroupId}\"\n          >\n            Source\n            <svg\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              stroke-width=\"2\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n            >\n              <polyline points=\"6 9 12 15 18 9\"></polyline>\n            </svg>\n          </button>\n\n          ${reactCode ? ` ${htmlButton} ${reactButton} ` : ''}\n\n          ${noCodePen ? '' : codePenButton}\n        </div>\n      </div>\n    `;\n\n    pre.insertAdjacentHTML('afterend', codePreview);\n    pre.remove();\n\n    if (adjacentPre) {\n      adjacentPre.remove();\n    }\n  });\n\n  // Wrap code preview scripts in anonymous functions so they don't run in the global scope\n  doc.querySelectorAll('.code-preview__preview script').forEach(script => {\n    if (script.type === 'module') {\n      // Modules are already scoped\n      script.textContent = script.innerHTML;\n    } else {\n      // Wrap non-modules in an anonymous function so they don't run in the global scope\n      script.textContent = `(() => { ${script.innerHTML} })();`;\n    }\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/copy-code-buttons.cjs",
    "content": "let codeBlockId = 0;\n\n/**\n * Adds copy code buttons to code fields. The provided doc should be a document object provided by JSDOM. The same\n * document will be returned with the appropriate DOM manipulations.\n */\nmodule.exports = function (doc) {\n  doc.querySelectorAll('pre > code').forEach(code => {\n    const pre = code.closest('pre');\n    const button = doc.createElement('sl-copy-button');\n\n    if (!code.id) {\n      code.id = `code-block-${++codeBlockId}`;\n    }\n\n    button.classList.add('copy-code-button');\n    button.setAttribute('from', code.id);\n\n    pre.append(button);\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/external-links.cjs",
    "content": "const { isExternalLink } = require('./strings.cjs');\n\n/**\n * Transforms external links to make them safer and optionally add a target. The provided doc should be a document\n * object provided by JSDOM. The same document will be returned with the appropriate DOM manipulations.\n */\nmodule.exports = function (doc, options) {\n  options = {\n    className: 'external-link', // the class name to add to links\n    noopener: true, // sets rel=\"noopener\"\n    noreferrer: true, // sets rel=\"noreferrer\"\n    ignore: () => false, // callback function to filter links that should be ignored\n    within: 'body', // element that contains the target links\n    target: '', // sets the target attribute\n    ...options\n  };\n\n  const within = doc.querySelector(options.within);\n\n  if (within) {\n    within.querySelectorAll('a').forEach(link => {\n      if (isExternalLink(link) && !options.ignore(link)) {\n        link.classList.add(options.className);\n\n        const rel = [];\n        if (options.noopener) rel.push('noopener');\n        if (options.noreferrer) rel.push('noreferrer');\n\n        if (rel.length) {\n          link.setAttribute('rel', rel.join(' '));\n        }\n\n        if (options.target) {\n          link.setAttribute('target', options.target);\n        }\n      }\n    });\n  }\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/highlight-code.cjs",
    "content": "const Prism = require('prismjs');\nconst PrismLoader = require('prismjs/components/index.js');\n\nPrismLoader('diff');\nPrismLoader.silent = true;\n\n/** Highlights a code string. */\nfunction highlight(code, language) {\n  const alias = language.replace(/^diff-/, '');\n  const isDiff = /^diff-/i.test(language);\n\n  // Auto-load the target language\n  if (!Prism.languages[alias]) {\n    PrismLoader(alias);\n\n    if (!Prism.languages[alias]) {\n      throw new Error(`Unsupported language for code highlighting: \"${language}\"`);\n    }\n  }\n\n  // Register diff-* languages to use the diff grammar\n  if (isDiff) {\n    Prism.languages[language] = Prism.languages.diff;\n  }\n\n  return Prism.highlight(code, Prism.languages[language], language);\n}\n\n/**\n * Highlights all code fields that have a language parameter. If the language has a colon in its name, the first chunk\n * will be the language used and additional chunks will be applied as classes to the `<pre>`. For example, a code field\n * tagged with \"html:preview\" will be rendered as `<pre class=\"language-html preview\">`.\n *\n * The provided doc should be a document object provided by JSDOM. The same document will be returned with the\n * appropriate DOM manipulations.\n */\nmodule.exports = function (doc) {\n  doc.querySelectorAll('pre > code[class]').forEach(code => {\n    // Look for class=\"language-*\" and split colons into separate classes\n    code.classList.forEach(className => {\n      if (className.startsWith('language-')) {\n        //\n        // We use certain suffixes to indicate code previews, expanded states, etc. The class might look something like\n        // this:\n        //\n        //  class=\"language-html:preview:expanded\"\n        //\n        // The language will always come first, so we need to drop the \"language-\" prefix and everything after the first\n        // color to get the highlighter language.\n        //\n        const language = className.replace(/^language-/, '').split(':')[0];\n\n        try {\n          code.innerHTML = highlight(code.textContent ?? '', language);\n        } catch (err) {\n          // Language not found, skip it\n        }\n      }\n    });\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/markdown.cjs",
    "content": "const MarkdownIt = require('markdown-it');\nconst markdownItContainer = require('markdown-it-container');\nconst markdownItIns = require('markdown-it-ins');\nconst markdownItKbd = require('markdown-it-kbd');\nconst markdownItMark = require('markdown-it-mark');\nconst markdownItReplaceIt = require('markdown-it-replace-it');\n\nconst markdown = MarkdownIt({\n  html: true,\n  xhtmlOut: false,\n  breaks: false,\n  langPrefix: 'language-',\n  linkify: false,\n  typographer: false\n});\n\n// Third-party plugins\nmarkdown.use(markdownItContainer);\nmarkdown.use(markdownItIns);\nmarkdown.use(markdownItKbd);\nmarkdown.use(markdownItMark);\nmarkdown.use(markdownItReplaceIt);\n\n// Callouts\n['tip', 'warning', 'danger'].forEach(type => {\n  markdown.use(markdownItContainer, type, {\n    render: function (tokens, idx) {\n      if (tokens[idx].nesting === 1) {\n        return `<div role=\"alert\" class=\"callout callout--${type}\">`;\n      }\n      return '</div>\\n';\n    }\n  });\n});\n\n// Asides\nmarkdown.use(markdownItContainer, 'aside', {\n  render: function (tokens, idx) {\n    if (tokens[idx].nesting === 1) {\n      return `<aside>`;\n    }\n    return '</aside>\\n';\n  }\n});\n\n// Details\nmarkdown.use(markdownItContainer, 'details', {\n  validate: params => params.trim().match(/^details\\s+(.*)$/),\n  render: (tokens, idx) => {\n    const m = tokens[idx].info.trim().match(/^details\\s+(.*)$/);\n    if (tokens[idx].nesting === 1) {\n      return `<details>\\n<summary><span>${markdown.utils.escapeHtml(m[1])}</span></summary>\\n`;\n    }\n    return '</details>\\n';\n  }\n});\n\n// Replace [#1234] with a link to GitHub issues\nmarkdownItReplaceIt.replacements.push({\n  name: 'github-issues',\n  re: /\\[#([0-9]+)\\]/gs,\n  sub: '<a href=\"https://github.com/shoelace-style/shoelace/issues/$1\">#$1</a>',\n  html: true,\n  default: true\n});\n\nmodule.exports = markdown;\n"
  },
  {
    "path": "docs/_utilities/prettier.cjs",
    "content": "const { format } = require('prettier');\n\n/** Formats markup using prettier. */\nmodule.exports = function (content, options) {\n  options = {\n    arrowParens: 'avoid',\n    bracketSpacing: true,\n    htmlWhitespaceSensitivity: 'css',\n    insertPragma: false,\n    bracketSameLine: false,\n    jsxSingleQuote: false,\n    parser: 'html',\n    printWidth: 120,\n    proseWrap: 'preserve',\n    quoteProps: 'as-needed',\n    requirePragma: false,\n    semi: true,\n    singleQuote: true,\n    tabWidth: 2,\n    trailingComma: 'none',\n    useTabs: false,\n    ...options\n  };\n\n  return format(content, options);\n};\n"
  },
  {
    "path": "docs/_utilities/replacer.cjs",
    "content": "/**\n * @typedef {object} Replacement\n * @property {string | RegExp} pattern\n * @property {string} replacement\n */\n\n/**\n * @typedef {Array<Replacement>} Replacements\n */\n\n/**\n * @param {String} rawContent\n * @param {Replacements} replacements\n */\nmodule.exports = function (rawContent, replacements) {\n  let content = rawContent;\n  replacements.forEach(replacement => {\n    content = content.replaceAll(replacement.pattern, replacement.replacement);\n  });\n\n  return content;\n};\n"
  },
  {
    "path": "docs/_utilities/scrolling-tables.cjs",
    "content": "/**\n * Turns headings into clickable, deep linkable anchors. The provided doc should be a document object provided by JSDOM.\n * The same document will be returned with the appropriate DOM manipulations.\n */\nmodule.exports = function (doc, options) {\n  const tables = [...doc.querySelectorAll('table')];\n\n  options = {\n    className: 'table-scroll', // the class name to add to the table's container\n    ...options\n  };\n\n  tables.forEach(table => {\n    const div = doc.createElement('div');\n    div.classList.add(options.className);\n    table.insertAdjacentElement('beforebegin', div);\n    div.append(table);\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/strings.cjs",
    "content": "const slugify = require('slugify');\n\n/** Creates a slug from an arbitrary string of text. */\nmodule.exports.createSlug = function (text) {\n  return slugify(String(text), {\n    remove: /[^\\w|\\s]/g,\n    lower: true\n  });\n};\n\n/** Determines whether or not a link is external. */\nmodule.exports.isExternalLink = function (link) {\n  // We use the \"internal\" hostname when initializing JSDOM so we know that those are local links\n  if (!link.hostname || link.hostname === 'internal') return false;\n  return true;\n};\n"
  },
  {
    "path": "docs/_utilities/table-of-contents.cjs",
    "content": "/**\n * Generates an in-page table of contents based on headings.\n */\nmodule.exports = function (doc, options) {\n  options = {\n    levels: ['h2'], // headings to include (they must have an id)\n    container: 'nav', // the container to append links to\n    listItem: true, // if true, links will be wrapped in <li>\n    within: 'body', // the element containing the headings to summarize\n    ...options\n  };\n\n  const container = doc.querySelector(options.container);\n  const within = doc.querySelector(options.within);\n  const headingSelector = options.levels.map(h => `${h}[id]`).join(', ');\n\n  if (!container || !within) {\n    return doc;\n  }\n\n  within.querySelectorAll(headingSelector).forEach(heading => {\n    const listItem = doc.createElement('li');\n    const link = doc.createElement('a');\n    const level = heading.tagName.slice(1);\n\n    link.href = `#${heading.id}`;\n    link.textContent = heading.textContent;\n\n    if (options.listItem) {\n      // List item + link\n      listItem.setAttribute('data-level', level);\n      listItem.append(link);\n      container.append(listItem);\n    } else {\n      // Link only\n      link.setAttribute('data-level', level);\n      container.append(link);\n    }\n  });\n\n  return doc;\n};\n"
  },
  {
    "path": "docs/_utilities/typography.cjs",
    "content": "const smartquotes = require('smartquotes');\n\nsmartquotes.replacements.push([/---/g, '\\u2014']); // em dash\nsmartquotes.replacements.push([/--/g, '\\u2013']); // en dash\nsmartquotes.replacements.push([/\\.\\.\\./g, '\\u2026']); // ellipsis\nsmartquotes.replacements.push([/\\(c\\)/gi, '\\u00A9']); // copyright\nsmartquotes.replacements.push([/\\(r\\)/gi, '\\u00AE']); // registered trademark\nsmartquotes.replacements.push([/\\?!/g, '\\u2048']); // ?!\nsmartquotes.replacements.push([/!!/g, '\\u203C']); // !!\nsmartquotes.replacements.push([/\\?\\?/g, '\\u2047']); // ??\nsmartquotes.replacements.push([/([0-9]\\s?)-(\\s?[0-9])/g, '$1\\u2013$2']); // number ranges use en dash\n\n/**\n * Improves typography by adding smart quotes and similar corrections within the specified element(s).\n *\n * The provided doc should be a document object provided by JSDOM. The same document will be returned with the\n * appropriate DOM manipulations.\n */\nmodule.exports = function (doc, selector = 'body') {\n  const elements = [...doc.querySelectorAll(selector)];\n  elements.forEach(el => smartquotes.element(el));\n  return doc;\n};\n"
  },
  {
    "path": "docs/assets/examples/include.html",
    "content": "<p style=\"margin-top: 0\">\n  The content in this example was included from\n  <a href=\"/assets/examples/include.html\" target=\"_blank\">a separate file</a>. 🤯\n</p>\n<p>\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n  aliqua. Lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi. Fringilla urna porttitor rhoncus dolor purus\n  non enim. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Gravida in fermentum et sollicitudin.\n</p>\n<p>\n  Cursus sit amet dictum sit amet justo donec enim. Sed id semper risus in hendrerit gravida. Viverra accumsan in nisl\n  nisi scelerisque eu ultrices vitae. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit. Nec\n  ullamcorper sit amet risus nullam. Et egestas quis ipsum suspendisse ultrices gravida dictum. Lorem donec massa sapien\n  faucibus et molestie. A cras semper auctor neque vitae.\n</p>\n\n<script>\n  console.log('This will only execute if the `allow-scripts` prop is present');\n</script>\n"
  },
  {
    "path": "docs/assets/scripts/code-previews.js",
    "content": "(() => {\n  function convertModuleLinks(html) {\n    html = html\n      .replace(/@shoelace-style\\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}`)\n      .replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)\n      .replace(/from \"react\"/g, `from \"https://esm.sh/react@${reactVersion}\"`);\n\n    return html;\n  }\n\n  function getAdjacentExample(name, pre) {\n    let currentPre = pre.nextElementSibling;\n\n    while (currentPre?.tagName.toLowerCase() === 'pre') {\n      if (currentPre?.getAttribute('data-lang').split(' ').includes(name)) {\n        return currentPre;\n      }\n\n      currentPre = currentPre.nextElementSibling;\n    }\n\n    return null;\n  }\n\n  function runScript(script) {\n    const newScript = document.createElement('script');\n\n    if (script.type === 'module') {\n      newScript.type = 'module';\n      newScript.textContent = script.innerHTML;\n    } else {\n      newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));\n    }\n\n    script.parentNode.replaceChild(newScript, script);\n  }\n\n  function getFlavor() {\n    return sessionStorage.getItem('flavor') || 'html';\n  }\n\n  function setFlavor(newFlavor) {\n    flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';\n    sessionStorage.setItem('flavor', flavor);\n\n    // Set the flavor class on the body\n    document.documentElement.classList.toggle('flavor-html', flavor === 'html');\n    document.documentElement.classList.toggle('flavor-react', flavor === 'react');\n  }\n\n  function syncFlavor() {\n    setFlavor(getFlavor());\n\n    document.querySelectorAll('.code-preview__button--html').forEach(preview => {\n      if (flavor === 'html') {\n        preview.classList.add('code-preview__button--selected');\n      }\n    });\n\n    document.querySelectorAll('.code-preview__button--react').forEach(preview => {\n      if (flavor === 'react') {\n        preview.classList.add('code-preview__button--selected');\n      }\n    });\n  }\n\n  const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');\n  const reactVersion = '^18';\n  const cdndir = 'cdn';\n  const npmdir = 'dist';\n  let flavor = getFlavor();\n  let count = 1;\n\n  // We need the version to open\n  if (!shoelaceVersion) {\n    throw new Error('The data-shoelace-version attribute is missing from <html>.');\n  }\n\n  // Sync flavor UI on page load\n  syncFlavor();\n\n  //\n  // Resizing previews\n  //\n  document.addEventListener('mousedown', handleResizerDrag);\n  document.addEventListener('touchstart', handleResizerDrag, { passive: true });\n\n  function handleResizerDrag(event) {\n    const resizer = event.target.closest('.code-preview__resizer');\n    const preview = event.target.closest('.code-preview__preview');\n\n    if (!resizer || !preview) return;\n\n    let startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;\n    let startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);\n\n    event.preventDefault();\n    preview.classList.add('code-preview__preview--dragging');\n    document.documentElement.addEventListener('mousemove', dragMove);\n    document.documentElement.addEventListener('touchmove', dragMove);\n    document.documentElement.addEventListener('mouseup', dragStop);\n    document.documentElement.addEventListener('touchend', dragStop);\n\n    function dragMove(event) {\n      const width = startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX;\n      preview.style.width = `${width}px`;\n    }\n\n    function dragStop() {\n      preview.classList.remove('code-preview__preview--dragging');\n      document.documentElement.removeEventListener('mousemove', dragMove);\n      document.documentElement.removeEventListener('touchmove', dragMove);\n      document.documentElement.removeEventListener('mouseup', dragStop);\n      document.documentElement.removeEventListener('touchend', dragStop);\n    }\n  }\n\n  //\n  // Toggle source mode\n  //\n  document.addEventListener('click', event => {\n    const button = event.target.closest('.code-preview__button');\n    const codeBlock = button?.closest('.code-preview');\n\n    if (button?.classList.contains('code-preview__button--html')) {\n      // Show HTML\n      setFlavor('html');\n      toggleSource(codeBlock, true);\n    } else if (button?.classList.contains('code-preview__button--react')) {\n      // Show React\n      setFlavor('react');\n      toggleSource(codeBlock, true);\n    } else if (button?.classList.contains('code-preview__toggle')) {\n      // Toggle source\n      toggleSource(codeBlock);\n    } else {\n      return;\n    }\n\n    // Update flavor buttons\n    [...document.querySelectorAll('.code-preview')].forEach(cb => {\n      cb.querySelector('.code-preview__button--html')?.classList.toggle(\n        'code-preview__button--selected',\n        flavor === 'html'\n      );\n      cb.querySelector('.code-preview__button--react')?.classList.toggle(\n        'code-preview__button--selected',\n        flavor === 'react'\n      );\n    });\n  });\n\n  function toggleSource(codeBlock, force) {\n    codeBlock.classList.toggle('code-preview--expanded', force);\n    event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));\n  }\n\n  //\n  // Open in CodePen\n  //\n  document.addEventListener('click', event => {\n    const button = event.target.closest('button');\n\n    if (button?.classList.contains('code-preview__button--codepen')) {\n      const codeBlock = button.closest('.code-preview');\n      const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;\n      const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;\n      const isReact = flavor === 'react' && typeof reactExample === 'string';\n      const theme = document.documentElement.classList.contains('sl-theme-dark') ? 'dark' : 'light';\n      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n      const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);\n      const editors = isReact ? '0010' : '1000';\n      let htmlTemplate = '';\n      let jsTemplate = '';\n      let cssTemplate = '';\n\n      const form = document.createElement('form');\n      form.action = 'https://codepen.io/pen/define';\n      form.method = 'POST';\n      form.target = '_blank';\n\n      // HTML templates\n      if (!isReact) {\n        htmlTemplate =\n          `<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/shoelace.js\"></script>\\n` +\n          `\\n${htmlExample}`;\n        jsTemplate = '';\n      }\n\n      // React templates\n      if (isReact) {\n        htmlTemplate = '<div id=\"root\"></div>';\n        jsTemplate =\n          `import React from 'https://esm.sh/react@${reactVersion}';\\n` +\n          `import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\\n` +\n          `import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\\n` +\n          `\\n` +\n          `// Set the base path for Shoelace assets\\n` +\n          `setBasePath('https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\\n` +\n          `\\n${convertModuleLinks(reactExample)}\\n` +\n          `\\n` +\n          `ReactDOM.render(<App />, document.getElementById('root'));`;\n      }\n\n      // CSS templates\n      cssTemplate =\n        `@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${\n          isDark ? 'dark' : 'light'\n        }.css';\\n` +\n        '\\n' +\n        'body {\\n' +\n        '  font: 16px sans-serif;\\n' +\n        '  background-color: var(--sl-color-neutral-0);\\n' +\n        '  color: var(--sl-color-neutral-900);\\n' +\n        '  padding: 1rem;\\n' +\n        '}';\n\n      // Docs: https://blog.codepen.io/documentation/prefill/\n      const data = {\n        title: '',\n        description: '',\n        tags: ['shoelace', 'web components'],\n        editors,\n        head: `<meta name=\"viewport\" content=\"width=device-width\">`,\n        html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,\n        css_external: ``,\n        js_external: ``,\n        js_module: true,\n        js_pre_processor: isReact ? 'babel' : 'none',\n        html: htmlTemplate,\n        css: cssTemplate,\n        js: jsTemplate\n      };\n\n      const input = document.createElement('input');\n      input.type = 'hidden';\n      input.name = 'data';\n      input.value = JSON.stringify(data);\n      form.append(input);\n\n      document.documentElement.append(form);\n      form.submit();\n      form.remove();\n    }\n  });\n\n  // Set the initial flavor\n  window.addEventListener('turbo:load', syncFlavor);\n})();\n"
  },
  {
    "path": "docs/assets/scripts/docs.js",
    "content": "//\n// Sidebar\n//\n// When the sidebar is hidden, we apply the inert attribute to prevent focus from reaching it. Due to the many states\n// the sidebar can have (e.g. static, hidden, expanded), we test for visibility by checking to see if it's placed\n// offscreen or not. Then, on resize/transition we make sure to update the attribute accordingly.\n//\n(() => {\n  function getSidebar() {\n    return document.getElementById('sidebar');\n  }\n\n  function isSidebarOpen() {\n    return document.documentElement.classList.contains('sidebar-open');\n  }\n\n  function isSidebarVisible() {\n    return getSidebar().getBoundingClientRect().x >= 0;\n  }\n\n  function toggleSidebar(force) {\n    const isOpen = typeof force === 'boolean' ? force : !isSidebarOpen();\n    return document.documentElement.classList.toggle('sidebar-open', isOpen);\n  }\n\n  function updateInert() {\n    getSidebar().inert = !isSidebarVisible();\n  }\n\n  // Toggle the menu\n  document.addEventListener('click', event => {\n    const menuToggle = event.target.closest('#menu-toggle');\n    if (!menuToggle) return;\n    toggleSidebar();\n  });\n\n  // Update the sidebar's inert state when the window resizes and when the sidebar transitions\n  window.addEventListener('resize', () => toggleSidebar(false));\n\n  document.addEventListener('transitionend', event => {\n    const sidebar = event.target.closest('#sidebar');\n    if (!sidebar) return;\n    updateInert();\n  });\n\n  // Close when a menu item is selected on mobile\n  document.addEventListener('click', event => {\n    const sidebar = event.target.closest('#sidebar');\n    const link = event.target.closest('a');\n    if (!sidebar || !link) return;\n\n    if (isSidebarOpen()) {\n      toggleSidebar();\n    }\n  });\n\n  // Close when open and escape is pressed\n  document.addEventListener('keydown', event => {\n    if (event.key === 'Escape' && isSidebarOpen()) {\n      event.stopImmediatePropagation();\n      toggleSidebar();\n    }\n  });\n\n  // Close when clicking outside of the sidebar\n  document.addEventListener('mousedown', event => {\n    if (isSidebarOpen() & !event.target?.closest('#sidebar, #menu-toggle')) {\n      event.stopImmediatePropagation();\n      toggleSidebar();\n    }\n  });\n\n  updateInert();\n})();\n\n//\n// Theme selector\n//\n(() => {\n  function getTheme() {\n    return localStorage.getItem('theme') || 'auto';\n  }\n\n  function isDark() {\n    if (theme === 'auto') {\n      return window.matchMedia('(prefers-color-scheme: dark)').matches;\n    }\n    return theme === 'dark';\n  }\n\n  function setTheme(newTheme) {\n    theme = newTheme;\n    localStorage.setItem('theme', theme);\n\n    // Update the UI\n    updateSelection();\n\n    // Toggle the dark mode class\n    document.documentElement.classList.toggle('sl-theme-dark', isDark());\n  }\n\n  function updateSelection() {\n    const menu = document.querySelector('#theme-selector sl-menu');\n    if (!menu) return;\n    [...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));\n  }\n\n  let theme = getTheme();\n\n  // Selection is not preserved when changing page, so update when opening dropdown\n  document.addEventListener('sl-show', event => {\n    const themeSelector = event.target.closest('#theme-selector');\n    if (!themeSelector) return;\n    updateSelection();\n  });\n\n  // Listen for selections\n  document.addEventListener('sl-select', event => {\n    const menu = event.target.closest('#theme-selector sl-menu');\n    if (!menu) return;\n    setTheme(event.detail.item.value);\n  });\n\n  // Update the theme when the preference changes\n  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));\n\n  // Toggle with backslash\n  document.addEventListener('keydown', event => {\n    if (\n      event.key === '\\\\' &&\n      !event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))\n    ) {\n      event.preventDefault();\n      setTheme(isDark() ? 'light' : 'dark');\n    }\n  });\n\n  // Set the initial theme and sync the UI\n  setTheme(theme);\n})();\n\n//\n// Open details when printing\n//\n(() => {\n  const detailsOpenOnPrint = new Set();\n\n  window.addEventListener('beforeprint', () => {\n    detailsOpenOnPrint.clear();\n    document.querySelectorAll('details').forEach(details => {\n      if (details.open) {\n        detailsOpenOnPrint.add(details);\n      }\n      details.open = true;\n    });\n  });\n\n  window.addEventListener('afterprint', () => {\n    document.querySelectorAll('details').forEach(details => {\n      details.open = detailsOpenOnPrint.has(details);\n    });\n    detailsOpenOnPrint.clear();\n  });\n})();\n\n//\n// Smooth links\n//\n(() => {\n  document.addEventListener('click', event => {\n    const link = event.target.closest('a');\n    const id = (link?.hash ?? '').substr(1);\n    const isFragment = link?.hasAttribute('href') && link?.getAttribute('href').startsWith('#');\n\n    if (!link || !isFragment || link.getAttribute('data-smooth-link') === 'false') {\n      return;\n    }\n\n    // Scroll to the top\n    if (link.hash === '') {\n      event.preventDefault();\n      window.scroll({ top: 0, behavior: 'smooth' });\n      history.pushState(undefined, undefined, location.pathname);\n    }\n\n    // Scroll to an id\n    if (id) {\n      const target = document.getElementById(id);\n\n      if (target) {\n        event.preventDefault();\n        window.scroll({ top: target.offsetTop, behavior: 'smooth' });\n        history.pushState(undefined, undefined, `#${id}`);\n      }\n    }\n  });\n})();\n\n//\n// Table of Contents scrollspy\n//\n(() => {\n  // This will be stale if its not a function.\n  const getLinks = () => [...document.querySelectorAll('.content__toc a')];\n  const linkTargets = new WeakMap();\n  const visibleTargets = new WeakSet();\n  const observer = new IntersectionObserver(handleIntersect, { rootMargin: '0px 0px' });\n  let debounce;\n\n  function handleIntersect(entries) {\n    entries.forEach(entry => {\n      // Remember which targets are visible\n      if (entry.isIntersecting) {\n        visibleTargets.add(entry.target);\n      } else {\n        visibleTargets.delete(entry.target);\n      }\n    });\n\n    updateActiveLinks();\n  }\n\n  function updateActiveLinks() {\n    const links = getLinks();\n    // Find the first visible target and activate the respective link\n    links.find(link => {\n      const target = linkTargets.get(link);\n\n      if (target && visibleTargets.has(target)) {\n        links.forEach(el => el.classList.toggle('active', el === link));\n        return true;\n      }\n\n      return false;\n    });\n  }\n\n  // Observe link targets\n  function observeLinks() {\n    getLinks().forEach(link => {\n      const hash = link.hash.slice(1);\n      const target = hash ? document.querySelector(`.content__body #${hash}`) : null;\n\n      if (target) {\n        linkTargets.set(link, target);\n        observer.observe(target);\n      }\n    });\n  }\n\n  observeLinks();\n\n  document.addEventListener('turbo:load', updateActiveLinks);\n  document.addEventListener('turbo:load', observeLinks);\n})();\n\n//\n// Show custom versions in the sidebar\n//\n(() => {\n  function updateVersion() {\n    const el = document.querySelector('.sidebar-version');\n    if (!el) return;\n\n    if (location.hostname === 'next.shoelace.style') el.textContent = 'Next';\n    if (location.hostname === 'localhost') el.textContent = 'Development';\n  }\n\n  updateVersion();\n\n  document.addEventListener('turbo:load', updateVersion);\n})();\n"
  },
  {
    "path": "docs/assets/scripts/search.js",
    "content": "(() => {\n  // Append the search dialog to the body\n  const siteSearch = document.createElement('div');\n  const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth);\n\n  siteSearch.classList.add('search');\n  siteSearch.innerHTML = `\n    <div class=\"search__overlay\"></div>\n    <dialog id=\"search-dialog\" class=\"search__dialog\">\n      <div class=\"search__content\">\n        <div class=\"search__header\">\n          <div id=\"search-combobox\" class=\"search__input-wrapper\">\n            <sl-icon name=\"search\"></sl-icon>\n            <input\n              id=\"search-input\"\n              class=\"search__input\"\n              type=\"search\"\n              placeholder=\"Search\"\n              autocomplete=\"off\"\n              autocorrect=\"off\"\n              autocapitalize=\"off\"\n              enterkeyhint=\"go\"\n              spellcheck=\"false\"\n              maxlength=\"100\"\n              role=\"combobox\"\n              aria-autocomplete=\"list\"\n              aria-expanded=\"true\"\n              aria-controls=\"search-listbox\"\n              aria-haspopup=\"listbox\"\n              aria-activedescendant\n            >\n            <button type=\"button\" class=\"search__clear-button\" aria-label=\"Clear entry\" tabindex=\"-1\" hidden>\n              <sl-icon name=\"x-circle-fill\"></sl-icon>\n            </button>\n          </div>\n        </div>\n        <div class=\"search__body\">\n          <ul\n            id=\"search-listbox\"\n            class=\"search__results\"\n            role=\"listbox\"\n            aria-label=\"Search results\"\n          ></ul>\n          <div class=\"search__empty\">No matching pages</div>\n        </div>\n        <footer class=\"search__footer\">\n          <small><kbd>↑</kbd> <kbd>↓</kbd> Navigate</small>\n          <small><kbd>↲</kbd> Select</small>\n          <small><kbd>Esc</kbd> Close</small>\n        </footer>\n      </div>\n    </dialog>\n  `;\n\n  const overlay = siteSearch.querySelector('.search__overlay');\n  const dialog = siteSearch.querySelector('.search__dialog');\n  const input = siteSearch.querySelector('.search__input');\n  const clearButton = siteSearch.querySelector('.search__clear-button');\n  const results = siteSearch.querySelector('.search__results');\n  const version = document.documentElement.getAttribute('data-shoelace-version');\n  const key = `search_${version}`;\n  const searchDebounce = 50;\n  const animationDuration = 150;\n  let isShowing = false;\n  let searchTimeout;\n  let searchIndex;\n  let map;\n\n  const loadSearchIndex = new Promise(resolve => {\n    const cache = localStorage.getItem(key);\n    const wait = 'requestIdleCallback' in window ? requestIdleCallback : requestAnimationFrame;\n\n    // Cleanup older search indices (everything before this version)\n    try {\n      const items = { ...localStorage };\n\n      Object.keys(items).forEach(k => {\n        if (key > k) {\n          localStorage.removeItem(k);\n        }\n      });\n    } catch {\n      /* do nothing */\n    }\n\n    // Look for a cached index\n    try {\n      if (cache) {\n        const data = JSON.parse(cache);\n\n        searchIndex = window.lunr.Index.load(data.searchIndex);\n        map = data.map;\n\n        return resolve();\n      }\n    } catch {\n      /* do nothing */\n    }\n\n    // Wait until idle to fetch the index\n    wait(() => {\n      fetch('/assets/search.json')\n        .then(res => res.json())\n        .then(data => {\n          if (!window.lunr) {\n            console.error('The Lunr search client has not yet been loaded.');\n          }\n\n          searchIndex = window.lunr.Index.load(data.searchIndex);\n          map = data.map;\n\n          // Cache the search index for this version\n          if (version) {\n            try {\n              localStorage.setItem(key, JSON.stringify(data));\n            } catch (err) {\n              console.warn(`Unable to cache the search index: ${err}`);\n            }\n          }\n\n          resolve();\n        });\n    });\n  });\n\n  async function show() {\n    isShowing = true;\n    document.body.append(siteSearch);\n    document.body.classList.add('search-visible');\n    document.body.style.setProperty('--docs-search-scroll-lock-size', `${scrollbarWidth}px`);\n    clearButton.hidden = true;\n    requestAnimationFrame(() => input.focus());\n    updateResults();\n\n    dialog.showModal();\n\n    await Promise.all([\n      dialog.animate(\n        [\n          { opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' },\n          { opacity: 1, transform: 'scale(1)', transformOrigin: 'top' }\n        ],\n        { duration: animationDuration }\n      ).finished,\n      overlay.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animationDuration }).finished\n    ]);\n\n    dialog.addEventListener('mousedown', handleMouseDown);\n    dialog.addEventListener('keydown', handleKeyDown);\n  }\n\n  async function hide() {\n    isShowing = false;\n\n    await Promise.all([\n      dialog.animate(\n        [\n          { opacity: 1, transform: 'scale(1)', transformOrigin: 'top' },\n          { opacity: 0, transform: 'scale(.9)', transformOrigin: 'top' }\n        ],\n        { duration: animationDuration }\n      ).finished,\n      overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: animationDuration }).finished\n    ]);\n\n    dialog.close();\n\n    input.blur(); // otherwise Safari will scroll to the bottom of the page on close\n    input.value = '';\n    document.body.classList.remove('search-visible');\n    document.body.style.removeProperty('--docs-search-scroll-lock-size');\n    siteSearch.remove();\n    updateResults();\n\n    dialog.removeEventListener('mousedown', handleMouseDown);\n    dialog.removeEventListener('keydown', handleKeyDown);\n  }\n\n  function handleInput() {\n    clearButton.hidden = input.value === '';\n\n    // Debounce search queries\n    clearTimeout(searchTimeout);\n    searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);\n  }\n\n  function handleClear() {\n    clearButton.hidden = true;\n    input.value = '';\n    input.focus();\n    updateResults();\n  }\n\n  function handleMouseDown(event) {\n    if (!event.target.closest('.search__content')) {\n      hide();\n    }\n  }\n\n  function handleKeyDown(event) {\n    // Close when pressing escape\n    if (event.key === 'Escape') {\n      event.preventDefault(); // prevent <dialog> from closing immediately so it can animate\n      event.stopImmediatePropagation();\n      hide();\n      return;\n    }\n\n    // Handle keyboard selections\n    if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {\n      event.preventDefault();\n\n      const currentEl = results.querySelector('[data-selected=\"true\"]');\n      const items = [...results.querySelectorAll('li')];\n      const index = items.indexOf(currentEl);\n      let nextEl;\n\n      if (items.length === 0) {\n        return;\n      }\n\n      switch (event.key) {\n        case 'ArrowUp':\n          nextEl = items[Math.max(0, index - 1)];\n          break;\n        case 'ArrowDown':\n          nextEl = items[Math.min(items.length - 1, index + 1)];\n          break;\n        case 'Home':\n          nextEl = items[0];\n          break;\n        case 'End':\n          nextEl = items[items.length - 1];\n          break;\n        case 'Enter':\n          currentEl?.querySelector('a')?.click();\n          break;\n      }\n\n      // Update the selected item\n      items.forEach(item => {\n        if (item === nextEl) {\n          input.setAttribute('aria-activedescendant', item.id);\n          item.setAttribute('data-selected', 'true');\n          nextEl.scrollIntoView({ block: 'nearest' });\n        } else {\n          item.setAttribute('data-selected', 'false');\n        }\n      });\n    }\n  }\n\n  async function updateResults(query = '') {\n    try {\n      await loadSearchIndex;\n\n      const hasQuery = query.length > 0;\n      const searchTerms = query\n        .split(' ')\n        .map((term, index, arr) => {\n          // Search API: https://lunrjs.com/guides/searching.html\n          if (index === arr.length - 1) {\n            // The last term is not mandatory and 1x fuzzy. We also duplicate it with a wildcard to match partial words\n            // as the user types.\n            return `${term}~1 ${term}*`;\n          } else {\n            // All other terms are mandatory and 1x fuzzy\n            return `+${term}~1`;\n          }\n        })\n        .join(' ');\n      const matches = hasQuery ? searchIndex.search(searchTerms) : [];\n      const hasResults = hasQuery && matches.length > 0;\n\n      siteSearch.classList.toggle('search--has-results', hasQuery && hasResults);\n      siteSearch.classList.toggle('search--no-results', hasQuery && !hasResults);\n\n      input.setAttribute('aria-activedescendant', '');\n      results.innerHTML = '';\n\n      matches.forEach((match, index) => {\n        const page = map[match.ref];\n        const li = document.createElement('li');\n        const a = document.createElement('a');\n        const displayTitle = page.title ?? '';\n        const displayDescription = page.description ?? '';\n        const displayUrl = page.url.replace(/^\\//, '').replace(/\\/$/, '');\n        let icon = 'file-text';\n\n        a.setAttribute('role', 'option');\n        a.setAttribute('id', `search-result-item-${match.ref}`);\n\n        if (page.url.includes('getting-started/')) {\n          icon = 'lightbulb';\n        }\n        if (page.url.includes('resources/')) {\n          icon = 'book';\n        }\n        if (page.url.includes('components/')) {\n          icon = 'puzzle';\n        }\n        if (page.url.includes('tokens/')) {\n          icon = 'palette2';\n        }\n        if (page.url.includes('utilities/')) {\n          icon = 'wrench';\n        }\n        if (page.url.includes('tutorials/')) {\n          icon = 'joystick';\n        }\n\n        li.classList.add('search__result');\n        li.setAttribute('role', 'option');\n        li.setAttribute('id', `search-result-item-${match.ref}`);\n        li.setAttribute('data-selected', index === 0 ? 'true' : 'false');\n\n        a.href = page.url;\n        a.innerHTML = `\n          <div class=\"search__result-icon\" aria-hidden=\"true\">\n            <sl-icon name=\"${icon}\"></sl-icon>\n          </div>\n          <div class=\"search__result__details\">\n            <div class=\"search__result-title\"></div>\n            <div class=\"search__result-description\"></div>\n            <div class=\"search__result-url\"></div>\n          </div>\n        `;\n        a.querySelector('.search__result-title').textContent = displayTitle;\n        a.querySelector('.search__result-description').textContent = displayDescription;\n        a.querySelector('.search__result-url').textContent = displayUrl;\n\n        li.appendChild(a);\n        results.appendChild(li);\n      });\n    } catch {\n      // Ignore query errors as the user types\n    }\n  }\n\n  // Show the search dialog when clicking on data-plugin=\"search\"\n  document.addEventListener('click', event => {\n    const searchButton = event.target.closest('[data-plugin=\"search\"]');\n    if (searchButton) {\n      show();\n    }\n  });\n\n  // Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element\n  document.addEventListener('keydown', event => {\n    if (\n      !isShowing &&\n      (event.key === '/' || (event.key === 'k' && (event.metaKey || event.ctrlKey))) &&\n      !event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))\n    ) {\n      event.preventDefault();\n      show();\n    }\n  });\n\n  // Purge cache when we press CMD+CTRL+R\n  document.addEventListener('keydown', event => {\n    if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'r') {\n      localStorage.clear();\n    }\n  });\n\n  input.addEventListener('input', handleInput);\n  clearButton.addEventListener('click', handleClear);\n\n  // Close when a result is selected\n  results.addEventListener('click', event => {\n    if (event.target.closest('a')) {\n      hide();\n    }\n  });\n\n  // We're using Turbo, so when a user searches for something, visits a result, and presses the back button, the search\n  // UI will still be visible but not interactive. This removes the search UI when Turbo renders a page so they don't\n  // get trapped.\n  window.addEventListener('turbo:render', () => {\n    document.body.classList.remove('search-visible');\n    document.querySelectorAll('.search__overlay, .search__dialog').forEach(el => el.remove());\n  });\n})();\n"
  },
  {
    "path": "docs/assets/scripts/turbo.js",
    "content": "import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm';\n\n(() => {\n  if (!window.scrollPositions) {\n    window.scrollPositions = {};\n  }\n\n  function preserveScroll() {\n    document.querySelectorAll('[data-preserve-scroll]').forEach(element => {\n      scrollPositions[element.id] = element.scrollTop;\n    });\n  }\n\n  function restoreScroll(event) {\n    document.querySelectorAll('[data-preserve-scroll]').forEach(element => {\n      element.scrollTop = scrollPositions[element.id];\n    });\n\n    if (event.detail && event.detail.newBody) {\n      event.detail.newBody.querySelectorAll('[data-preserve-scroll').forEach(element => {\n        element.scrollTop = scrollPositions[element.id];\n      });\n    }\n  }\n\n  window.addEventListener('turbo:before-cache', preserveScroll);\n  window.addEventListener('turbo:before-render', restoreScroll);\n  window.addEventListener('turbo:render', restoreScroll);\n})();\n"
  },
  {
    "path": "docs/assets/styles/code-previews.css",
    "content": "/* Interactive code blocks */\n.code-preview {\n  position: relative;\n  border-radius: 3px;\n  background-color: var(--sl-color-neutral-50);\n  margin-bottom: 1.5rem;\n}\n\n.code-preview__preview {\n  position: relative;\n  border: solid 1px var(--sl-color-neutral-200);\n  border-bottom: none;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n  background-color: var(--sl-color-neutral-0);\n  min-width: 20rem;\n  max-width: 100%;\n  padding: 1.5rem 3.25rem 1.5rem 1.5rem;\n}\n\n/* Block the preview while dragging to prevent iframes from intercepting drag events */\n.code-preview__preview--dragging:after {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  opacity: 0;\n  cursor: ew-resize;\n}\n\n.code-preview__resizer {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  width: 1.75rem;\n  font-size: 20px;\n  color: var(--sl-color-neutral-600);\n  background-color: var(--sl-color-neutral-0);\n  border-left: solid 1px var(--sl-color-neutral-200);\n  border-top-right-radius: 3px;\n  cursor: ew-resize;\n}\n\n@media screen and (max-width: 600px) {\n  .code-preview__preview {\n    padding-right: 1.5rem;\n  }\n\n  .code-preview__resizer {\n    display: none;\n  }\n}\n\n.code-preview__source {\n  border: solid 1px var(--sl-color-neutral-200);\n  border-bottom: none;\n  border-radius: 0 !important;\n  display: none;\n}\n\n.code-preview--expanded .code-preview__source {\n  display: block;\n}\n\n.code-preview__source pre {\n  margin: 0;\n}\n\n.code-preview__buttons {\n  position: relative;\n  border: solid 1px var(--sl-color-neutral-200);\n  border-bottom-left-radius: 3px;\n  border-bottom-right-radius: 3px;\n  display: flex;\n}\n\n.code-preview__button {\n  flex: 0 0 auto;\n  height: 2.5rem;\n  min-width: 2.5rem;\n  border: none;\n  border-radius: 0;\n  background: var(--sl-color-neutral-0);\n  font: inherit;\n  font-size: 0.7rem;\n  font-weight: 500;\n  text-transform: uppercase;\n  color: var(--sl-color-neutral-600);\n  padding: 0 1rem;\n  cursor: pointer;\n}\n\n.code-preview__button:not(:last-of-type) {\n  border-right: solid 1px var(--sl-color-neutral-200);\n}\n\n.code-preview__button--html,\n.code-preview__button--react {\n  width: 70px;\n  display: flex;\n  place-items: center;\n  justify-content: center;\n}\n\n.code-preview__button--selected {\n  font-weight: 700;\n  color: var(--sl-color-primary-600);\n}\n\n.code-preview__button--codepen {\n  display: flex;\n  place-items: center;\n  width: 6rem;\n}\n\n.code-preview__button:first-of-type {\n  border-bottom-left-radius: 3px;\n}\n\n.code-preview__button:last-of-type {\n  border-bottom-right-radius: 3px;\n}\n\n.code-preview__button:hover,\n.code-preview__button:active {\n  box-shadow: 0 0 0 1px var(--sl-color-primary-400);\n  border-right-color: transparent;\n  background-color: var(--sl-color-primary-50);\n  color: var(--sl-color-primary-600);\n  z-index: 1;\n}\n\n.code-preview__button:focus-visible {\n  outline: none;\n  outline: var(--sl-focus-ring);\n  z-index: 2;\n}\n\n.code-preview__toggle {\n  position: relative;\n  display: flex;\n  flex: 1 1 auto;\n  align-items: center;\n  justify-content: center;\n  width: 100%;\n  color: var(--sl-color-neutral-600);\n  cursor: pointer;\n}\n\n.code-preview__toggle svg {\n  width: 1em;\n  height: 1em;\n  margin-left: 0.25rem;\n}\n\n.code-preview--expanded .code-preview__toggle svg {\n  transform: rotate(180deg);\n}\n\n/* We can apply data-flavor=\"html|react\" to any element on the page to toggle it when the flavor changes */\n.flavor-html [data-flavor]:not([data-flavor='html']) {\n  display: none;\n}\n\n.flavor-react [data-flavor]:not([data-flavor='react']) {\n  display: none;\n}\n"
  },
  {
    "path": "docs/assets/styles/docs.css",
    "content": ":root {\n  --docs-background-color: var(--sl-color-neutral-0);\n  --docs-border-color: var(--sl-color-neutral-200);\n  --docs-border-width: 1px;\n  --docs-border-radius: var(--sl-border-radius-medium);\n  --docs-content-max-width: 860px;\n  --docs-sidebar-width: 320px;\n  --docs-sidebar-transition-speed: 250ms;\n  --docs-content-toc-max-width: 260px;\n  --docs-content-padding: 2rem;\n  --docs-content-vertical-spacing: 2rem;\n  --docs-search-overlay-background: rgb(0 0 0 / 0.2);\n  --docs-skip-to-main-width: 200px;\n}\n\n/* Light theme */\n:root {\n  color-scheme: normal;\n\n  --sl-color-primary-50: var(--sl-color-sky-50);\n  --sl-color-primary-100: var(--sl-color-sky-100);\n  --sl-color-primary-200: var(--sl-color-sky-200);\n  --sl-color-primary-300: var(--sl-color-sky-300);\n  --sl-color-primary-400: var(--sl-color-sky-400);\n  --sl-color-primary-500: var(--sl-color-sky-500);\n  --sl-color-primary-600: var(--sl-color-sky-600);\n  --sl-color-primary-700: var(--sl-color-sky-700);\n  --sl-color-primary-800: var(--sl-color-sky-800);\n  --sl-color-primary-900: var(--sl-color-sky-900);\n  --sl-color-primary-950: var(--sl-color-sky-950);\n\n  --docs-overlay-color: hsl(240 3.8% 46.1% / 33%);\n  --docs-shadow-x-small: 0 1px 2px hsl(240 3.8% 46.1% / 12%);\n  --docs-shadow-small: 0 1px 2px hsl(240 3.8% 46.1% / 24%);\n  --docs-shadow-medium: 0 2px 4px hsl(240 3.8% 46.1% / 24%);\n  --docs-shadow-large: 0 2px 8px hsl(240 3.8% 46.1% / 24%);\n  --docs-shadow-x-large: 0 4px 16px hsl(240 3.8% 46.1% / 24%);\n}\n\n/* Dark theme */\n.sl-theme-dark {\n  color-scheme: dark;\n\n  --docs-overlay-color: hsl(0 0% 0% / 66%);\n  --docs-shadow-x-small: 0 1px 2px rgb(0 0 0 / 36%);\n  --docs-shadow-small: 0 1px 2px rgb(0 0 0 / 48%);\n  --docs-shadow-medium: 0 2px 4px rgb(0 0 0 / 48%);\n  --docs-shadow-large: 0 2px 8px rgb(0 0 0 / 48%);\n  --docs-shadow-x-large: 0 4px 16px rgb(0 0 0 / 48%);\n}\n\n/* Utils */\nhtml.sl-theme-dark .only-light,\nhtml:not(.sl-theme-dark) .only-dark {\n  display: none !important;\n}\n\n.visually-hidden:not(:focus-within) {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  clip: rect(0 0 0 0) !important;\n  clip-path: inset(50%) !important;\n  border: none !important;\n  overflow: hidden !important;\n  white-space: nowrap !important;\n  padding: 0 !important;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n@media screen and (max-width: 900px) {\n  :root {\n    --docs-content-padding: 1rem;\n  }\n}\n\n/* Bare styles */\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\na:focus,\nbutton:focus {\n  outline: none;\n}\n\na:focus-visible,\nbutton:focus-visible {\n  outline: var(--sl-focus-ring);\n  outline-offset: var(--sl-focus-ring-offset);\n}\n\n::selection {\n  background-color: var(--sl-color-primary-200);\n  color: var(--sl-color-neutral-900);\n}\n\n/* Show custom elements only after they're registered */\n:not(:defined),\n:not(:defined) * {\n  opacity: 0;\n}\n\n:defined {\n  opacity: 1;\n  transition: 0.1s opacity;\n}\n\nhtml {\n  height: 100%;\n  box-sizing: border-box;\n  line-height: var(--sl-line-height-normal);\n  padding: 0;\n  margin: 0;\n}\n\nbody {\n  height: 100%;\n  font: 16px var(--sl-font-sans);\n  font-weight: var(--sl-font-weight-normal);\n  background-color: var(--docs-background-color);\n  line-height: var(--sl-line-height-normal);\n  color: var(--sl-color-neutral-900);\n  padding: 0;\n  margin: 0;\n  overflow-x: hidden;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n}\n\n/* Common elements */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: var(--sl-font-sans);\n  font-weight: var(--sl-font-weight-semibold);\n  margin: 3rem 0 1.5rem 0;\n}\n\nh1:first-of-type {\n  margin-top: 1rem;\n}\n\nh1 {\n  font-size: 2.5rem;\n}\n\nh2 {\n  font-size: 1.875rem;\n  border-bottom: solid var(--docs-border-width) var(--docs-border-color);\n}\n\nh3 {\n  font-size: 1.625rem;\n}\n\nh4 {\n  font-size: 1.375rem;\n}\n\nh5 {\n  font-size: 1.125rem;\n}\n\nh6 {\n  font-size: 0.875rem;\n}\n\np {\n  margin-block-end: 1.5em;\n}\n\nimg {\n  max-width: 100%;\n}\n\n.badges img {\n  border-radius: var(--sl-border-radius-medium);\n}\n\n.callout img,\ndetails img {\n  width: 100%;\n  margin-left: 0;\n  margin-right: 0;\n}\n\ndetails pre {\n  border: solid var(--docs-border-width) var(--docs-border-color);\n}\n\na {\n  color: var(--sl-color-primary-700);\n}\n\na:hover {\n  color: var(--sl-color-primary-800);\n}\n\nabbr[title] {\n  text-decoration: none;\n  border-bottom: dashed 1px var(--sl-color-primary-700);\n  cursor: help;\n}\n\nem {\n  font-style: italic;\n}\n\nstrong {\n  font-weight: var(--sl-font-weight-bold);\n}\n\ncode {\n  font-family: var(--sl-font-mono);\n  font-size: 0.9125em;\n  background-color: rgba(0 0 0 / 0.025);\n  background-blend-mode: darken;\n  border-radius: var(--docs-border-radius);\n  padding: 0.125em 0.25em;\n}\n\n.sl-theme-dark code {\n  background-color: rgba(255 255 255 / 0.03);\n}\n\nkbd {\n  background: var(--sl-color-neutral-100);\n  border: solid 1px var(--sl-color-neutral-200);\n  box-shadow:\n    inset 0 1px 0 0 var(--sl-color-neutral-0),\n    inset 0 -1px 0 0 var(--sl-color-neutral-200);\n  font-family: var(--sl-font-mono);\n  font-size: 0.9125em;\n  border-radius: var(--docs-border-radius);\n  color: var(--sl-color-neutral-800);\n  padding: 0.125em 0.4em;\n}\n\nins {\n  background-color: var(--sl-color-green-200);\n  color: var(--sl-color-green-900);\n  border-radius: var(--docs-border-radius);\n  text-decoration: none;\n  padding: 0.125em 0.25em;\n}\n\ns {\n  background-color: var(--sl-color-red-200);\n  color: var(--sl-color-red-900);\n  border-radius: var(--docs-border-radius);\n  text-decoration: none;\n  padding: 0.125em 0.25em;\n}\n\nmark {\n  background-color: var(--sl-color-yellow-200);\n  border-radius: var(--docs-border-radius);\n  padding: 0.125em 0.25em;\n}\n\nhr {\n  border: none;\n  border-bottom: solid var(--docs-border-width) var(--docs-border-color);\n  margin: calc(var(--docs-content-vertical-spacing) * 2) 0;\n}\n\n/* Block quotes */\nblockquote {\n  position: relative;\n  font-family: var(--sl-font-serif);\n  font-size: 1.33rem;\n  font-style: italic;\n  color: var(--sl-color-neutral-700);\n  background-color: var(--sl-color-neutral-100);\n  border-radius: var(--docs-border-radius);\n  border-left: solid 6px var(--sl-color-neutral-300);\n  padding: 1.5rem;\n  margin: 0 0 1.5rem 0;\n}\n\nblockquote > :first-child {\n  margin-top: 0;\n}\n\nblockquote > :last-child {\n  margin-bottom: 0;\n}\n\n/* Lists */\nul,\nol {\n  padding: 0;\n  margin: 0 0 var(--docs-content-vertical-spacing) 2rem;\n}\n\nul {\n  list-style: disc;\n}\n\nli {\n  padding: 0;\n  margin: 0 0 0.25rem 0;\n}\n\nli ul,\nli ol {\n  margin-top: 0.25rem;\n}\n\nul ul:last-child,\nul ol:last-child,\nol ul:last-child,\nol ol:last-child {\n  margin-bottom: 0;\n}\n\n/* Anchor headings */\n.anchor-heading {\n  position: relative;\n  color: inherit;\n  text-decoration: none;\n}\n\n.anchor-heading a {\n  text-decoration: none;\n  color: inherit;\n}\n\n.anchor-heading a::after {\n  content: '#';\n  color: var(--sl-color-primary-700);\n  margin-inline: 0.5rem;\n  opacity: 0;\n  transition: 100ms opacity;\n}\n\n.anchor-heading:hover a::after,\n.anchor-heading:focus-within a::after {\n  opacity: 1;\n}\n\n/* External links */\n.external-link__icon {\n  width: 0.75em;\n  height: 0.75em;\n  vertical-align: 0;\n  margin-left: 0.25em;\n  margin-right: 0.125em;\n}\n\n/* Tables */\ntable {\n  max-width: 100%;\n  border: none;\n  border-collapse: collapse;\n  color: inherit;\n  margin-bottom: var(--docs-content-vertical-spacing);\n}\n\ntable tr {\n  border-bottom: solid var(--docs-border-width) var(--docs-border-color);\n}\n\ntable th {\n  font-weight: var(--sl-font-weight-semibold);\n  text-align: left;\n}\n\ntable td,\ntable th {\n  line-height: var(--sl-line-height-normal);\n  padding: 1rem 1rem;\n}\n\ntable th p:first-child,\ntable td p:first-child {\n  margin-top: 0;\n}\n\ntable th p:last-child,\ntable td p:last-child {\n  margin-bottom: 0;\n}\n\n.table-scroll {\n  max-width: 100%;\n  overflow-x: auto;\n}\n\n.table-scroll code {\n  white-space: nowrap;\n}\n\nth.table-name,\nth.table-event-detail {\n  min-width: 15ch;\n}\n\nth.table-description {\n  min-width: 50ch !important;\n  max-width: 70ch;\n}\n\n/* Code blocks */\npre {\n  position: relative;\n  background-color: var(--sl-color-neutral-50);\n  font-family: var(--sl-font-mono);\n  color: var(--sl-color-neutral-900);\n  border-radius: var(--docs-border-radius);\n  padding: 1rem;\n  white-space: pre;\n}\n\n.sl-theme-dark pre {\n  background-color: var(--sl-color-neutral-50);\n}\n\npre:not(:last-child) {\n  margin-bottom: 1.5rem;\n}\n\npre > code {\n  display: block;\n  background: none !important;\n  border-radius: 0;\n  hyphens: none;\n  tab-size: 2;\n  white-space: pre;\n  padding: 1rem;\n  margin: -1rem;\n  overflow: auto;\n}\n\npre .token.comment {\n  color: var(--sl-color-neutral-600);\n}\n\npre .token.prolog,\npre .token.doctype,\npre .token.cdata,\npre .token.operator,\npre .token.punctuation {\n  color: var(--sl-color-neutral-700);\n}\n\n.namespace {\n  opacity: 0.7;\n}\n\npre .token.property,\npre .token.keyword,\npre .token.tag,\npre .token.url {\n  color: var(--sl-color-blue-700);\n}\n\npre .token.symbol,\npre .token.deleted {\n  color: var(--sl-color-red-700);\n}\n\npre .token.boolean,\npre .token.constant,\npre .token.selector,\npre .token.attr-name,\npre .token.string,\npre .token.char,\npre .token.builtin,\npre .token.inserted {\n  color: var(--sl-color-emerald-700);\n}\n\npre .token.atrule,\npre .token.attr-value,\npre .token.number,\npre .token.variable {\n  color: var(--sl-color-violet-700);\n}\n\npre .token.function,\npre .token.class-name,\npre .token.regex {\n  color: var(--sl-color-orange-700);\n}\n\npre .token.important {\n  color: var(--sl-color-red-700);\n}\n\npre .token.important,\npre .token.bold {\n  font-weight: bold;\n}\n\npre .token.italic {\n  font-style: italic;\n}\n\n/* Copy code button */\n.copy-code-button {\n  position: absolute;\n  top: 0;\n  right: 0;\n  white-space: normal;\n  color: var(--sl-color-neutral-800);\n  transition:\n    150ms opacity,\n    150ms scale;\n}\n\n.copy-code-button::part(button) {\n  background-color: var(--sl-color-neutral-50);\n  border-radius: 0 var(--docs-border-radius) 0 var(--docs-border-radius);\n  padding: 0.75rem;\n}\n\n.copy-code-button::part(button):hover {\n  background-color: color-mix(in oklch, var(--sl-color-neutral-50), var(--sl-color-neutral-1000) 3%);\n}\n\n.copy-code-button::part(button):active {\n  background-color: color-mix(in oklch, var(--sl-color-neutral-50), var(--sl-color-neutral-1000) 6%);\n}\n\npre .copy-code-button {\n  opacity: 0;\n  scale: 0.75;\n}\n\npre:hover .copy-code-button,\n.copy-code-button:focus-within {\n  opacity: 1;\n  scale: 1;\n}\n\n/* Callouts */\n.callout {\n  position: relative;\n  background-color: var(--sl-color-neutral-100);\n  border-left: solid 4px var(--sl-color-neutral-500);\n  border-radius: var(--docs-border-radius);\n  color: var(--sl-color-neutral-800);\n  padding: 1.5rem 1.5rem 1.5rem 2rem;\n  margin-bottom: var(--docs-content-vertical-spacing);\n}\n\n.callout > :first-child {\n  margin-top: 0;\n}\n\n.callout > :last-child {\n  margin-bottom: 0;\n}\n\n.callout--tip {\n  background-color: var(--sl-color-primary-100);\n  border-left-color: var(--sl-color-primary-600);\n  color: var(--sl-color-primary-800);\n}\n\n.callout::before {\n  content: '';\n  position: absolute;\n  top: calc(50% - 0.8rem);\n  left: calc(-0.8rem - 2px);\n  width: 1.6rem;\n  height: 1.6rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-family: var(--sl-font-serif);\n  font-weight: var(--sl-font-weight-bold);\n  color: var(--sl-color-neutral-0);\n  clip-path: circle(50% at 50% 50%);\n}\n\n.callout--tip::before {\n  content: 'i';\n  font-style: italic;\n  background-color: var(--sl-color-primary-600);\n}\n\n.callout--warning {\n  background-color: var(--sl-color-warning-100);\n  border-left-color: var(--sl-color-warning-600);\n  color: var(--sl-color-warning-800);\n}\n\n.callout--warning::before {\n  content: '!';\n  background-color: var(--sl-color-warning-600);\n}\n\n.callout--danger {\n  background-color: var(--sl-color-danger-100);\n  border-left-color: var(--sl-color-danger-600);\n  color: var(--sl-color-danger-800);\n}\n\n.callout--danger::before {\n  content: '‼';\n  background-color: var(--sl-color-danger-600);\n}\n\n.callout + .callout {\n  margin-top: calc(-0.5 * var(--docs-content-vertical-spacing));\n}\n\n.callout a {\n  color: inherit;\n}\n\n/* Aside */\n.content aside {\n  float: right;\n  min-width: 300px;\n  max-width: 50%;\n  background: var(--sl-color-neutral-100);\n  border-radius: var(--docs-border-radius);\n  padding: 1rem;\n  margin-left: 1rem;\n}\n\n.content aside > :first-child {\n  margin-top: 0;\n}\n\n.content aside > :last-child {\n  margin-bottom: 0;\n}\n\n@media screen and (max-width: 600px) {\n  .content aside {\n    float: none;\n    width: calc(100% + (var(--docs-content-padding) * 2));\n    max-width: none;\n    margin: var(--docs-content-vertical-spacing) calc(-1 * var(--docs-content-padding));\n  }\n}\n\n/* Details */\n.content details {\n  background-color: var(--sl-color-neutral-100);\n  border-radius: var(--docs-border-radius);\n  padding: 1rem;\n  margin: 0 0 var(--docs-content-vertical-spacing) 0;\n}\n\n.content details summary {\n  font-weight: var(--sl-font-weight-semibold);\n  border-radius: var(--docs-border-radius);\n  padding: 1rem;\n  margin: -1rem;\n  cursor: pointer;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n.content details summary span {\n  padding-left: 0.5rem;\n}\n\n.content details[open] summary {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n  margin-bottom: 1rem;\n}\n\n.content details[open] summary:focus-visible {\n  border-radius: var(--docs-border-radius);\n}\n\n.content details > :last-child {\n  margin-bottom: 0;\n}\n\n.content details > :nth-child(2) {\n  margin-top: 0;\n}\n\n.content details + details {\n  margin-top: calc(-1 * var(--docs-content-vertical-spacing) + (2 * var(--docs-border-width)));\n}\n\n/* Sidebar */\n#sidebar {\n  position: fixed;\n  flex: 0;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  z-index: 20;\n  width: var(--docs-sidebar-width);\n  background-color: var(--docs-background-color);\n  border-right: solid var(--docs-border-width) var(--docs-border-color);\n  border-radius: 0;\n  padding: 2rem;\n  margin: 0;\n  overflow: auto;\n  scrollbar-width: thin;\n  transition: var(--docs-sidebar-transition-speed) translate ease-in-out;\n}\n\n#sidebar::-webkit-scrollbar {\n  width: 4px;\n}\n\n#sidebar::-webkit-scrollbar-thumb {\n  background: transparent;\n  border-radius: 9999px;\n}\n\n#sidebar:hover::-webkit-scrollbar-thumb {\n  background: var(--sl-color-neutral-400);\n}\n\n#sidebar:hover::-webkit-scrollbar-track {\n  background: var(--sl-color-neutral-100);\n}\n\n#sidebar > header {\n  margin-bottom: 1.5rem;\n}\n\n#sidebar > header h1 {\n  margin: 0;\n}\n\n#sidebar > header a {\n  display: block;\n}\n\n#sidebar nav a {\n  text-decoration: none;\n}\n\n#sidebar nav h2 {\n  font-size: var(--sl-font-size-medium);\n  font-weight: var(--sl-font-weight-semibold);\n  border-bottom: solid var(--docs-border-width) var(--docs-border-color);\n  margin: 1.5rem 0 0.5rem 0;\n}\n\n#sidebar ul {\n  padding: 0;\n  margin: 0;\n}\n\n#sidebar ul li {\n  list-style: none;\n  padding: 0;\n  margin: 0.125rem 0.5rem;\n}\n\n#sidebar ul ul ul {\n  margin-left: 0.75rem;\n}\n\n#sidebar ul li a {\n  line-height: 1.33;\n  color: inherit;\n  display: inline-block;\n  padding: 0;\n}\n\n#sidebar ul li a:not(.active-link):hover {\n  color: var(--sl-color-primary-700);\n}\n\n#sidebar nav .active-link {\n  color: var(--sl-color-primary-700);\n  border-bottom: dashed 1px var(--sl-color-primary-700);\n}\n\n#sidebar > header img {\n  display: block;\n  width: 100%;\n  height: auto;\n  margin: 0 auto;\n}\n\n@media screen and (max-width: 900px) {\n  #sidebar {\n    translate: -100%;\n  }\n\n  .sidebar-open #sidebar {\n    translate: 0;\n  }\n}\n\n.sidebar-version {\n  font-size: var(--sl-font-size-x-small);\n  color: var(--sl-color-neutral-500);\n  text-align: right;\n  margin-top: -0.5rem;\n  margin-right: 1rem;\n  margin-bottom: -0.5rem;\n}\n\n.sidebar-buttons {\n  display: flex;\n  justify-content: space-between;\n}\n\n/* Main content */\nmain {\n  position: relative;\n  padding: var(--docs-content-vertical-spacing) var(--docs-content-padding)\n    calc(var(--docs-content-vertical-spacing) * 2) var(--docs-content-padding);\n  margin-left: var(--docs-sidebar-width);\n}\n\n.sidebar-open .content {\n  margin-left: 0;\n}\n\n.content__body > :last-child {\n  margin-bottom: 0;\n}\n\n@media screen and (max-width: 900px) {\n  main {\n    margin-left: 0;\n  }\n}\n\n/* Component layouts */\n.content {\n  display: grid;\n  grid-template-columns: 100%;\n  gap: 2rem;\n  position: relative;\n  max-width: var(--docs-content-max-width);\n  margin: 0 auto;\n}\n\n.content--with-toc {\n  /* There's a 2rem gap, so we need to remove it from the column */\n  grid-template-columns: calc(75% - 2rem) min(25%, var(--docs-content-toc-max-width));\n  max-width: calc(var(--docs-content-max-width) + var(--docs-content-toc-max-width));\n}\n\n.content__body {\n  order: 1;\n  width: 100%;\n}\n\n.content:not(.content--with-toc) .content__toc {\n  display: none;\n}\n\n.content__toc {\n  order: 2;\n  display: flex;\n  flex-direction: column;\n  margin-top: 0;\n}\n\n.content__toc ul {\n  position: sticky;\n  top: 5rem;\n  max-height: calc(100vh - 6rem);\n  font-size: var(--sl-font-size-small);\n  line-height: 1.33;\n  border-left: solid 1px var(--sl-color-neutral-200);\n  list-style: none;\n  padding: 1rem 0;\n  margin: 0;\n  padding-left: 1rem;\n  overflow-y: auto;\n}\n\n.content__toc li {\n  padding: 0 0 0 0.5rem;\n  margin: 0;\n}\n\n.content__toc li[data-level='3'] {\n  margin-left: 1rem;\n}\n\n/* We don't use them, but just in case */\n.content__toc li[data-level='4'],\n.content__toc li[data-level='5'],\n.content__toc li[data-level='6'] {\n  margin-left: 2rem;\n}\n\n.content__toc li:not(:last-of-type) {\n  margin-bottom: 0.6rem;\n}\n\n.content__toc a {\n  color: var(--sl-color-neutral-700);\n  text-decoration: none;\n}\n\n.content__toc a:hover {\n  color: var(--sl-color-primary-700);\n}\n\n.content__toc a.active {\n  color: var(--sl-color-primary-700);\n  border-bottom: dashed 1px var(--sl-color-primary-700);\n}\n\n.content__toc .top a {\n  font-weight: var(--sl-font-weight-semibold);\n  color: var(--sl-color-neutral-500);\n}\n\n@media screen and (max-width: 1024px) {\n  .content {\n    grid-template-columns: 100%;\n    gap: 0;\n  }\n\n  .content__toc {\n    position: relative;\n    order: 1;\n  }\n\n  .content__toc ul {\n    display: flex;\n    justify-content: start;\n    gap: 1rem 1.5rem;\n    position: static;\n    border: none;\n    border-bottom: solid 1px var(--sl-color-neutral-200);\n    border-radius: 0;\n    padding: 1rem 1.5rem 1rem 0.5rem; /* extra right padding to hide the fade effect */\n    margin-top: 1rem;\n    overflow-x: auto;\n  }\n\n  .content__toc ul::after {\n    content: '';\n    position: absolute;\n    top: 0;\n    bottom: 1rem; /* don't cover the scrollbar */\n    right: 0;\n    width: 2rem;\n    background: linear-gradient(90deg, rgba(0 0 0 / 0) 0%, var(--sl-color-neutral-0) 100%);\n  }\n\n  .content__toc li {\n    white-space: nowrap;\n  }\n\n  .content__toc li:not(:last-of-type) {\n    margin-bottom: 0;\n  }\n\n  .content__toc [data-level]:not([data-level='2']) {\n    display: none;\n  }\n\n  .content__body {\n    order: 2;\n  }\n}\n\n/* Menu toggle */\n#menu-toggle {\n  display: none;\n  position: fixed;\n  z-index: 30;\n  top: 0.25rem;\n  left: 0.25rem;\n  height: auto;\n  width: auto;\n  color: black;\n  border: none;\n  border-radius: 50%;\n  background: var(--sl-color-neutral-0);\n  padding: 0.5rem;\n  margin: 0;\n  cursor: pointer;\n  transition:\n    250ms scale ease,\n    250ms rotate ease;\n}\n\n@media screen and (max-width: 900px) {\n  #menu-toggle {\n    display: flex;\n  }\n}\n\n.sl-theme-dark #menu-toggle {\n  color: white;\n}\n\n#menu-toggle:hover {\n  scale: 1.1;\n}\n\n#menu-toggle svg {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\nhtml.sidebar-open #menu-toggle {\n  rotate: 180deg;\n}\n\n/* Skip to main content */\n#skip-to-main {\n  position: fixed;\n  top: 0.25rem;\n  left: calc(50% - var(--docs-skip-to-main-width) / 2);\n  z-index: 100;\n  width: var(--docs-skip-to-main-width);\n  text-align: center;\n  text-decoration: none;\n  border-radius: 9999px;\n  background: var(--sl-color-neutral-0);\n  color: var(--sl-color-neutral-1000);\n  padding: 0.5rem;\n}\n\n/* Icon toolbar */\n#icon-toolbar {\n  display: flex;\n  position: fixed;\n  top: 0;\n  right: 1rem;\n  z-index: 10;\n  background: var(--sl-color-neutral-800);\n  border-bottom-left-radius: calc(var(--docs-border-radius) * 2);\n  border-bottom-right-radius: calc(var(--docs-border-radius) * 2);\n  padding: 0.125rem 0.25rem;\n}\n\n#icon-toolbar button,\n#icon-toolbar a {\n  flex: 0 0 auto;\n  display: inline-flex;\n  align-items: center;\n  width: auto;\n  height: auto;\n  background: transparent;\n  border: none;\n  border-radius: var(--docs-border-radius);\n  font-size: 1.25rem;\n  color: var(--sl-color-neutral-0);\n  text-decoration: none;\n  padding: 0.5rem;\n  margin: 0;\n  cursor: pointer;\n}\n\n#theme-selector:not(:defined) {\n  /* Hide when not defined to prevent extra wide icon toolbar while loading */\n  display: none;\n}\n\n#theme-selector sl-menu {\n  /* Set an initial size to prevent width being too small when first opening on small screen width */\n  width: 140px;\n}\n\n#theme-selector sl-button {\n  transition: 250ms scale ease;\n}\n\n#theme-selector sl-button::part(base) {\n  color: var(--sl-color-neutral-0);\n}\n\n#theme-selector sl-button::part(label) {\n  display: flex;\n  padding: 0.5rem;\n}\n\n#theme-selector sl-icon {\n  font-size: 1.25rem;\n}\n\n.sl-theme-dark #theme-selector sl-button::part(base) {\n  color: var(--sl-color-neutral-1000);\n}\n\n.sl-theme-dark #icon-toolbar {\n  background: var(--sl-color-neutral-200);\n}\n\n.sl-theme-dark #icon-toolbar button,\n.sl-theme-dark #icon-toolbar a {\n  color: var(--sl-color-neutral-1000);\n}\n\n#icon-toolbar a:not(:last-child),\n#icon-toolbar button:not(:last-child) {\n  margin-right: 0.25rem;\n}\n\n@media screen and (max-width: 900px) {\n  #icon-toolbar {\n    right: 0;\n    border-bottom-right-radius: 0;\n  }\n\n  #icon-toolbar button,\n  #icon-toolbar a {\n    font-size: 1rem;\n    padding: 0.5rem;\n  }\n\n  #theme-selector sl-icon {\n    font-size: 1rem;\n  }\n}\n\n/* Sidebar addons */\n#sidebar-addons:not(:empty) {\n  margin-bottom: var(--docs-content-vertical-spacing);\n}\n\n/* Print styles */\n@media print {\n  a:not(.anchor-heading)[href]::after {\n    content: ' (' attr(href) ')';\n  }\n\n  details,\n  pre,\n  .callout {\n    border: solid var(--docs-border-width) var(--docs-border-color);\n  }\n\n  details summary {\n    list-style: none;\n  }\n\n  details summary span {\n    padding-left: 0;\n  }\n\n  details summary::marker,\n  details summary::-webkit-details-marker {\n    display: none;\n  }\n\n  .callout::before {\n    border: solid var(--docs-border-width) var(--docs-border-color);\n  }\n\n  .component-page__navigation,\n  .copy-code-button,\n  .code-preview__buttons,\n  .code-preview__resizer {\n    display: none !important;\n  }\n\n  .flavor-html .code-preview__source--html,\n  .flavor-react .code-preview__source--react {\n    display: block !important;\n  }\n\n  .flavor-html .code-preview__source--html > pre,\n  .flavor-react .code-preview__source--react > pre {\n    border: none;\n  }\n\n  .code-preview__source-group {\n    border-bottom: solid 1px var(--sl-color-neutral-200);\n    border-bottom-left-radius: var(--sl-border-radius-medium);\n    border-bottom-right-radius: var(--sl-border-radius-medium);\n  }\n\n  #sidebar {\n    display: none;\n  }\n\n  #content {\n    margin-left: 0;\n  }\n\n  #menu-toggle,\n  #icon-toolbar,\n  .external-link__icon {\n    display: none;\n  }\n}\n\n/* Splash */\n.splash {\n  display: flex;\n  padding-top: 2rem;\n}\n\n.splash-start {\n  min-width: 420px;\n}\n\n.splash-start h1 {\n  font-size: var(--sl-font-size-large);\n  font-weight: var(--sl-font-weight-normal);\n}\n\n.splash li img {\n  width: 1em;\n  height: 1em;\n  vertical-align: -2px;\n}\n\n.splash-end {\n  display: flex;\n  align-items: flex-end;\n  width: auto;\n  padding-left: 1rem;\n}\n\n.splash-image {\n  width: 100%;\n  height: auto;\n}\n\n.splash-logo {\n  max-width: 22rem;\n}\n\n.splash-start h1:first-of-type {\n  font-size: var(--sl-font-size-large);\n  margin: 0 0 0.5rem 0;\n}\n\n@media screen and (max-width: 1280px) {\n  .splash {\n    display: block;\n  }\n\n  .splash-start {\n    min-width: 0;\n    padding-bottom: 1rem;\n  }\n\n  .splash-end {\n    padding: 0;\n  }\n\n  .splash-image {\n    display: block;\n    max-width: 400px;\n  }\n\n  /* Shields */\n  .splash + p {\n    margin-top: 2rem;\n  }\n}\n\n/* Component headers */\n.component-header h1 {\n  margin-bottom: 0;\n}\n\n.component-header__tag {\n  margin-top: -0.5rem;\n  margin-bottom: 0.5rem;\n}\n\n.component-header__tag code {\n  background: none;\n  color: var(--sl-color-neutral-600);\n  font-size: var(--sl-font-size-large);\n  padding: 0;\n  margin: 0;\n}\n\n.component-header__info {\n  margin-bottom: var(--sl-spacing-x-large);\n}\n\n.component-summary {\n  font-size: var(--sl-font-size-large);\n  line-height: 1.6;\n  margin: 2rem 0;\n}\n\n/* Repo buttons */\n.sidebar-buttons {\n  display: flex;\n  gap: 0.125rem;\n  justify-content: space-between;\n}\n\n.sidebar-buttons .repo-button {\n  flex: 1 1 auto;\n}\n\n.repo-button--github sl-icon {\n  color: var(--sl-color-neutral-700);\n}\n\n.repo-button--star sl-icon {\n  color: var(--sl-color-yellow-500);\n}\n\n.repo-button--twitter sl-icon {\n  color: var(--sl-color-sky-500);\n}\n\n@media screen and (max-width: 400px) {\n  :not(.sidebar-buttons) > .repo-button {\n    width: 100%;\n    margin-bottom: 1rem;\n  }\n}\n\nbody[data-page^='/tokens/'] .table-wrapper td:first-child,\nbody[data-page^='/tokens/'] .table-wrapper td:first-child code {\n  white-space: nowrap;\n}\n\n/* Border radius demo */\n.border-radius-demo {\n  width: 3rem;\n  height: 3rem;\n  background: var(--sl-color-primary-600);\n}\n\n/* Transition demo */\n.transition-demo {\n  position: relative;\n  background: var(--sl-color-neutral-200);\n  width: 8rem;\n  height: 2rem;\n}\n\n.transition-demo:after {\n  content: '';\n  position: absolute;\n  background-color: var(--sl-color-primary-600);\n  top: 0;\n  left: 0;\n  width: 0;\n  height: 100%;\n  transition-duration: inherit;\n  transition-property: width;\n}\n\n.transition-demo:hover:after {\n  width: 100%;\n}\n\n/* Spacing demo */\n.spacing-demo {\n  width: 100px;\n  background: var(--sl-color-primary-600);\n}\n\n/* Elevation demo */\n.elevation-demo {\n  background: transparent;\n  border-radius: 3px;\n  width: 4rem;\n  height: 4rem;\n  margin: 1rem;\n}\n\n/* Color palettes */\n.color-palette {\n  display: grid;\n  grid-template-columns: 200px repeat(11, 1fr);\n  gap: 1rem var(--sl-spacing-2x-small);\n  margin: 2rem 0;\n}\n\n.color-palette__name {\n  font-size: var(--sl-font-size-medium);\n  font-weight: var(--sl-font-weight-semibold);\n  grid-template-columns: repeat(11, 1fr);\n}\n\n.color-palette__name code {\n  background: none;\n  font-size: var(--sl-font-size-x-small);\n}\n\n.color-palette__example {\n  font-size: var(--sl-font-size-x-small);\n  text-align: center;\n}\n\n.color-palette__swatch {\n  height: 3rem;\n  border-radius: var(--sl-border-radius-small);\n}\n\n.color-palette__swatch--border {\n  box-shadow: inset 0 0 0 1px var(--sl-color-neutral-300);\n}\n\n@media screen and (max-width: 1200px) {\n  .color-palette {\n    grid-template-columns: repeat(6, 1fr);\n  }\n\n  .color-palette__name {\n    grid-column-start: span 6;\n  }\n}\n\n/* custom styling - WA dialog */\n#wa-dialog {\n  --action-color: #cd491c;\n  --action-color-hover: color-mix(in oklab, var(--action-color), var(--sl-color-neutral-1000) 5%);\n}\n\n#wa-dialog a {\n  color: var(--action-color);\n}\n\n#wa-dialog a:hover {\n  color: var(--action-color-hover);\n}\n\n#wa-dialog::part(header) {\n  display: none;\n}\n\n#wa-dialog::part(body) {\n  padding: 0;\n}\n\n#wa-dialog::part(footer) {\n  background-color: var(--sl-color-gray-50);\n  font-size: var(--sl-font-size-small);\n  text-align: start;\n}\n\n#wa-dialog::part(panel) {\n  overflow: clip; /* preserve border-radius of panel w/ footer + billboard backgrounds */\n}\n\n#wa-dialog .dialog-body {\n  padding: var(--sl-spacing-x-large) var(--body-spacing);\n}\n\n#wa-dialog .dialog-billboard {\n  position: relative;\n  padding: var(--sl-spacing-3x-large) var(--sl-spacing-2x-large);\n  gap: var(--sl-spacing-medium);\n  background: linear-gradient(to bottom, #f46a45, #cd491c);\n  color: var(--sl-color-neutral-0);\n}\n\n#wa-dialog .dialog-billboard::after {\n  content: '';\n  position: absolute;\n  inset: 0;\n  z-index: 0;\n  background-color: transparent;\n  background-image: url('/assets/images/wa-bg-pattern.svg');\n  background-repeat: repeat;\n  opacity: 0.3;\n}\n\n#wa-dialog .dialog-billboard-content {\n  position: relative;\n  z-index: 1;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: var(--sl-spacing-x-small);\n}\n\n#wa-dialog .dialog-billboard-content .wa-logo {\n  inline-size: auto;\n  max-inline-size: 100%;\n  block-size: 4rem;\n}\n\n#wa-dialog .dialog-billboard-content h2 {\n  margin-block: 0;\n  border-bottom: none;\n  text-align: center;\n  line-height: 1.4;\n  font-weight: var(--sl-font-weight-bold);\n  font-size: var(--sl-font-size-x-large);\n}\n\n#wa-dialog p {\n  margin-block-end: var(--sl-spacing-small);\n}\n\n#wa-dialog p:last-of-type {\n  margin-block-end: 0;\n}\n\n#wa-dialog h4,\n#wa-dialog p {\n  margin-block-start: 0;\n}\n\n#wa-dialog .dialog-actions {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--sl-spacing-x-small);\n  justify-content: space-between;\n  align-items: center;\n}\n\n#wa-dialog .dialog-actions .wa-ctas {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--sl-spacing-x-small);\n  align-items: center;\n}\n\n#wa-dialog sl-divider {\n  --spacing: var(--sl-spacing-x-large);\n}\n\n#wa-dialog .wa-cta::part(base) {\n  border-color: var(--action-color);\n}\n\n#wa-dialog .close-wa-dialog::part(label) {\n  color: var(--action-color);\n}\n\n#wa-dialog .close-wa-dialog::part(label):hover {\n  color: var(--action-color-hover);\n}\n\n#wa-dialog .close-wa-cta::part(label):hover {\n  color: var(--sl-color-neutral-0);\n}\n\n#wa-dialog .wa-cta::part(base) {\n  color: var(--action-color);\n}\n\n#wa-dialog .wa-pro-cta::part(base) {\n  background-color: var(--action-color);\n  border-color: var(--action-color);\n}\n\n#wa-dialog .wa-cta::part(base):hover,\n#wa-dialog .wa-pro-cta::part(base):hover {\n  background-color: var(--action-color-hover);\n  border-color: var(--action-color-hover);\n  color: var(--sl-color-neutral-0);\n}\n"
  },
  {
    "path": "docs/assets/styles/search.css",
    "content": "/* Search plugin */\n:root,\n:root.sl-theme-dark {\n  --docs-search-box-background: var(--sl-color-neutral-0);\n  --docs-search-box-border-width: 1px;\n  --docs-search-box-border-color: var(--sl-color-neutral-300);\n  --docs-search-box-color: var(--sl-color-neutral-600);\n  --docs-search-dialog-background: var(--sl-color-neutral-0);\n  --docs-search-border-width: var(--docs-border-width);\n  --docs-search-border-color: var(--docs-border-color);\n  --docs-search-text-color: var(--sl-color-neutral-900);\n  --docs-search-text-color-muted: var(--sl-color-neutral-500);\n  --docs-search-font-weight-normal: var(--sl-font-weight-normal);\n  --docs-search-font-weight-semibold: var(--sl-font-weight-semibold);\n  --docs-search-border-radius: calc(2 * var(--docs-border-radius));\n  --docs-search-accent-color: var(--sl-color-primary-600);\n  --docs-search-icon-color: var(--sl-color-neutral-500);\n  --docs-search-icon-color-active: var(--sl-color-neutral-600);\n  --docs-search-shadow: var(--docs-shadow-x-large);\n  --docs-search-result-background-hover: var(--sl-color-neutral-100);\n  --docs-search-result-color-hover: var(--sl-color-neutral-900);\n  --docs-search-result-background-active: var(--sl-color-primary-600);\n  --docs-search-result-color-active: var(--sl-color-neutral-0);\n  --docs-search-focus-ring: var(--sl-focus-ring);\n  --docs-search-overlay-background: rgb(0 0 0 / 0.33);\n}\n\n:root.sl-theme-dark {\n  --docs-search-overlay-background: rgb(71 71 71 / 0.33);\n}\n\nbody.search-visible {\n  padding-right: var(--docs-search-scroll-lock-size) !important;\n  overflow: hidden !important;\n}\n\n/* Search box */\n.search-box {\n  flex: 1 1 auto;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  border: none;\n  border-radius: 9999px;\n  background: var(--docs-search-box-background);\n  border: solid var(--docs-search-box-border-width) var(--docs-search-box-border-color);\n  font: inherit;\n  color: var(--docs-search-box-color);\n  padding: 0.75rem 1rem;\n  margin: var(--sl-spacing-large) 0;\n  cursor: pointer;\n}\n\n.search-box span {\n  flex: 1 1 auto;\n  width: 1rem;\n  height: 1rem;\n  text-align: left;\n  line-height: 1;\n  margin: 0 0.75rem;\n}\n\n.search-box:focus {\n  outline: none;\n}\n\n.search-box:focus-visible {\n  outline: var(--docs-search-focus-ring);\n}\n\n/* Site search */\n.search {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 9999;\n}\n\n.search[hidden] {\n  display: none;\n}\n\n.search__overlay {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: var(--docs-search-overlay-background);\n  z-index: -1;\n}\n\n.search__dialog {\n  width: 100%;\n  height: 100%;\n  max-width: none;\n  max-height: none;\n  background: transparent;\n  border: none;\n  padding: 0;\n  margin: 0;\n}\n\n.search__dialog:focus {\n  outline: none;\n}\n\n.search__dialog::backdrop {\n  display: none;\n}\n\n/* Fixes an iOS Safari 16.4 bug that draws the parent element's border radius incorrectly when showing/hiding results */\n.search__header {\n  background-color: var(--docs-search-dialog-background);\n  border-radius: var(--docs-search-border-radius);\n}\n\n.search--has-results .search__header {\n  border-top-left-radius: var(--docs-search-border-radius);\n  border-top-right-radius: var(--docs-search-border-radius);\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\n.search__content {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 500px;\n  max-height: calc(100vh - 20rem);\n  background-color: var(--docs-search-dialog-background);\n  border-radius: var(--docs-search-border-radius);\n  box-shadow: var(--docs-search-shadow);\n  padding: 0;\n  margin: 10rem auto;\n}\n\n@media screen and (max-width: 900px) {\n  .search__content {\n    max-width: calc(100% - 2rem);\n    max-height: calc(90svh);\n    margin: 4vh 1rem;\n  }\n}\n\n.search__input-wrapper {\n  display: flex;\n  align-items: center;\n}\n\n.search__input-wrapper sl-icon {\n  width: 1.5rem;\n  height: 1.5rem;\n  flex: 0 0 auto;\n  color: var(--docs-search-icon-color);\n  margin: 0 1.5rem;\n}\n\n.search__clear-button {\n  display: flex;\n  background: none;\n  border: none;\n  font: inherit;\n  padding: 0;\n  margin: 0;\n  cursor: pointer;\n}\n\n.search__clear-button[hidden] {\n  display: none;\n}\n\n.search__clear-button:active sl-icon {\n  color: var(--docs-search-icon-color-active);\n}\n\n.search__input {\n  flex: 1 1 auto;\n  min-width: 0;\n  border: none;\n  font: inherit;\n  font-size: 1.5rem;\n  font-weight: var(--docs-search-font-weight-normal);\n  color: var(--docs-search-text-color);\n  background: transparent;\n  padding: 1rem 0;\n  margin: 0;\n}\n\n.search__input::placeholder {\n  color: var(--docs-search-text-color-muted);\n}\n\n.search__input::-webkit-search-decoration,\n.search__input::-webkit-search-cancel-button,\n.search__input::-webkit-search-results-button,\n.search__input::-webkit-search-results-decoration {\n  display: none;\n}\n\n.search__input:focus,\n.search__input:focus-visible {\n  outline: none;\n}\n\n.search__body {\n  flex: 1 1 auto;\n  overflow: auto;\n}\n\n.search--has-results .search__body {\n  border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);\n}\n\n.search__results {\n  display: none;\n  line-height: 1.2;\n  list-style: none;\n  padding: 0.5rem 0;\n  margin: 0;\n}\n\n.search--has-results .search__results {\n  display: block;\n}\n\n.search__results a {\n  display: block;\n  text-decoration: none;\n  padding: 0.5rem 1.5rem;\n}\n\n.search__results a:focus-visible {\n  outline: var(--docs-search-focus-ring);\n}\n\n.search__results li a:hover,\n.search__results li a:hover small {\n  background-color: var(--docs-search-result-background-hover);\n  color: var(--docs-search-result-color-hover);\n}\n\n.search__results li[data-selected='true'] a,\n.search__results li[data-selected='true'] a * {\n  outline: none;\n  background-color: var(--docs-search-result-background-active);\n  color: var(--docs-search-result-color-active);\n}\n\n.search__results h3 {\n  font-weight: var(--docs-search-font-weight-semibold);\n  margin: 0;\n}\n\n.search__results small {\n  display: block;\n  color: var(--docs-search-text-color-muted);\n}\n\n.search__result {\n  padding: 0;\n  margin: 0;\n}\n\n.search__result a {\n  display: flex;\n  align-items: center;\n  gap: 1rem;\n}\n\n.search__result-icon {\n  flex: 0 0 auto;\n  display: flex;\n  color: var(--docs-search-text-color-muted);\n}\n\n.search__result-icon sl-icon {\n  font-size: 1.5rem;\n}\n\n.search__result__details {\n  width: calc(100% - 3rem);\n}\n\n.search__result-title,\n.search__result-description,\n.search__result-url {\n  max-width: 400px;\n  line-height: 1.3;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.search__result-title {\n  font-size: 1.2rem;\n  font-weight: var(--docs-search-font-weight-semibold);\n  color: var(--docs-search-accent-color);\n}\n\n.search__result-description {\n  font-size: 0.875rem;\n  color: var(--docs-search-text-color);\n}\n\n.search__result-url {\n  font-size: 0.875rem;\n  color: var(--docs-search-text-color-muted);\n}\n\n.search__empty {\n  display: none;\n  border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);\n  text-align: center;\n  color: var(--docs-search-text-color-muted);\n  padding: 2rem;\n}\n\n.search--no-results .search__empty {\n  display: block;\n}\n\n.search__footer {\n  display: flex;\n  justify-content: center;\n  gap: 2rem;\n  border-top: solid var(--docs-search-border-width) var(--docs-search-border-color);\n  border-bottom-left-radius: inherit;\n  border-bottom-right-radius: inherit;\n  padding: 1rem;\n}\n\n.search__footer small {\n  color: var(--docs-search-text-color-muted);\n}\n\n.search__footer small kbd:last-of-type {\n  margin-right: 0.25rem;\n}\n\n@media screen and (max-width: 900px) {\n  .search__footer {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "docs/eleventy.config.cjs",
    "content": "/* eslint-disable no-invalid-this */\nconst fs = require('fs');\nconst path = require('path');\nconst lunr = require('lunr');\nconst { capitalCase } = require('change-case');\nconst { JSDOM } = require('jsdom');\nconst { customElementsManifest, getAllComponents } = require('./_utilities/cem.cjs');\nconst shoelaceFlavoredMarkdown = require('./_utilities/markdown.cjs');\nconst activeLinks = require('./_utilities/active-links.cjs');\nconst anchorHeadings = require('./_utilities/anchor-headings.cjs');\nconst codePreviews = require('./_utilities/code-previews.cjs');\nconst copyCodeButtons = require('./_utilities/copy-code-buttons.cjs');\nconst externalLinks = require('./_utilities/external-links.cjs');\nconst highlightCodeBlocks = require('./_utilities/highlight-code.cjs');\nconst tableOfContents = require('./_utilities/table-of-contents.cjs');\nconst prettier = require('./_utilities/prettier.cjs');\nconst scrollingTables = require('./_utilities/scrolling-tables.cjs');\nconst typography = require('./_utilities/typography.cjs');\nconst replacer = require('./_utilities/replacer.cjs');\n\nconst assetsDir = 'assets';\nconst cdndir = 'cdn';\nconst npmdir = 'dist';\nconst allComponents = getAllComponents();\nlet hasBuiltSearchIndex = false;\n\nmodule.exports = function (eleventyConfig) {\n  //\n  // Global data\n  //\n  eleventyConfig.addGlobalData('baseUrl', 'https://shoelace.style/'); // the production URL\n  eleventyConfig.addGlobalData('layout', 'default'); // make 'default' the default layout\n  eleventyConfig.addGlobalData('toc', true); // enable the table of contents\n  eleventyConfig.addGlobalData('meta', {\n    title: 'Shoelace',\n    description: 'A forward-thinking library of web components.',\n    image: 'images/og-image.png',\n    version: customElementsManifest.package.version,\n    components: allComponents,\n    cdndir,\n    npmdir\n  });\n\n  //\n  // Layout aliases\n  //\n  eleventyConfig.addLayoutAlias('default', 'default.njk');\n\n  //\n  // Copy assets\n  //\n  eleventyConfig.addPassthroughCopy(assetsDir);\n  eleventyConfig.setServerPassthroughCopyBehavior('passthrough'); // emulates passthrough copy during --serve\n\n  //\n  // Functions\n  //\n\n  // Generates a URL relative to the site's root\n  eleventyConfig.addNunjucksGlobal('rootUrl', (value = '', absolute = false) => {\n    value = path.join('/', value);\n    return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;\n  });\n\n  // Generates a URL relative to the site's asset directory\n  eleventyConfig.addNunjucksGlobal('assetUrl', (value = '', absolute = false) => {\n    value = path.join(`/${assetsDir}`, value);\n    return absolute ? new URL(value, eleventyConfig.globalData.baseUrl).toString() : value;\n  });\n\n  // Fetches a specific component's metadata\n  eleventyConfig.addNunjucksGlobal('getComponent', tagName => {\n    const component = allComponents.find(c => c.tagName === tagName);\n    if (!component) {\n      throw new Error(\n        `Unable to find a component called \"${tagName}\". Make sure the file name is the same as the component's tag ` +\n          `name (minus the sl- prefix).`\n      );\n    }\n    return component;\n  });\n\n  //\n  // Custom markdown syntaxes\n  //\n  eleventyConfig.setLibrary('md', shoelaceFlavoredMarkdown);\n\n  //\n  // Filters\n  //\n  eleventyConfig.addFilter('markdown', content => {\n    return shoelaceFlavoredMarkdown.render(content);\n  });\n\n  eleventyConfig.addFilter('markdownInline', content => {\n    return shoelaceFlavoredMarkdown.renderInline(content);\n  });\n\n  // Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited.\n  // With Prettier 3, this means a leading pipe will exist if the line wraps.\n  eleventyConfig.addFilter('trimPipes', content => {\n    return typeof content === 'string' ? content.replace(/^(\\s|\\|)/g, '').replace(/(\\s|\\|)$/g, '') : content;\n  });\n\n  eleventyConfig.addFilter('classNameToComponentName', className => {\n    let name = capitalCase(className.replace(/^Sl/, ''));\n    if (name === 'Qr Code') name = 'QR Code'; // manual override\n    return name;\n  });\n\n  eleventyConfig.addFilter('removeSlPrefix', tagName => {\n    return tagName.replace(/^sl-/, '');\n  });\n\n  //\n  // Transforms\n  //\n  eleventyConfig.addTransform('html-transform', function (rawContent) {\n    let content = replacer(rawContent, [\n      { pattern: '%VERSION%', replacement: customElementsManifest.package.version },\n      { pattern: '%CDNDIR%', replacement: cdndir },\n      { pattern: '%NPMDIR%', replacement: npmdir }\n    ]);\n\n    // Parse the template and get a Document object\n    const doc = new JSDOM(content, {\n      // We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily\n      // identify which ones are internal and which ones are external.\n      url: `https://internal/`\n    }).window.document;\n\n    // DOM transforms\n    activeLinks(doc, { pathname: this.page.url });\n    anchorHeadings(doc, {\n      within: '#content .content__body',\n      levels: ['h2', 'h3', 'h4', 'h5']\n    });\n    tableOfContents(doc, {\n      levels: ['h2', 'h3'],\n      container: '#content .content__toc > ul',\n      within: '#content .content__body'\n    });\n    codePreviews(doc);\n    externalLinks(doc, { target: '_blank' });\n    highlightCodeBlocks(doc);\n    scrollingTables(doc);\n    copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks\n    typography(doc, '#content');\n\n    // Serialize the Document object to an HTML string and prepend the doctype\n    content = `<!DOCTYPE html>\\n${doc.documentElement.outerHTML}`;\n\n    // String transforms\n    content = prettier(content);\n\n    return content;\n  });\n\n  //\n  // Build a search index\n  //\n  eleventyConfig.on('eleventy.after', ({ results }) => {\n    // We only want to build the search index on the first run so all pages get indexed.\n    if (hasBuiltSearchIndex) {\n      return;\n    }\n\n    const map = {};\n    const searchIndexFilename = path.join(eleventyConfig.dir.output, assetsDir, 'search.json');\n    const lunrInput = path.resolve('../node_modules/lunr/lunr.min.js');\n    const lunrOutput = path.join(eleventyConfig.dir.output, assetsDir, 'scripts/lunr.js');\n    const searchIndex = lunr(function () {\n      // The search index uses these field names extensively, so shortening them can save some serious bytes. The\n      // initial index file went from 468 KB => 401 KB by using single-character names!\n      this.ref('id'); // id\n      this.field('t', { boost: 50 }); // title\n      this.field('h', { boost: 25 }); // headings\n      this.field('c'); // content\n\n      results.forEach((result, index) => {\n        const url = path\n          .join('/', path.relative(eleventyConfig.dir.output, result.outputPath))\n          .replace(/\\\\/g, '/') // convert backslashes to forward slashes\n          .replace(/\\/index.html$/, '/'); // convert trailing /index.html to /\n        const doc = new JSDOM(result.content, {\n          // We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily\n          // identify which ones are internal and which ones are external.\n          url: `https://internal/`\n        }).window.document;\n        const content = doc.querySelector('#content');\n\n        // Get title and headings\n        const title = (doc.querySelector('title')?.textContent || path.basename(result.outputPath)).trim();\n        const headings = [...content.querySelectorAll('h1, h2, h3, h4')]\n          .map(heading => heading.textContent)\n          .join(' ')\n          .replace(/\\s+/g, ' ')\n          .trim();\n\n        // Remove code blocks and whitespace from content\n        [...content.querySelectorAll('code[class|=language]')].forEach(code => code.remove());\n        const textContent = content.textContent.replace(/\\s+/g, ' ').trim();\n\n        // Update the index and map\n        this.add({ id: index, t: title, h: headings, c: textContent });\n        map[index] = { title, url };\n      });\n    });\n\n    // Copy the Lunr search client and write the index\n    fs.mkdirSync(path.dirname(lunrOutput), { recursive: true });\n    fs.copyFileSync(lunrInput, lunrOutput);\n    fs.writeFileSync(searchIndexFilename, JSON.stringify({ searchIndex, map }), 'utf-8');\n\n    hasBuiltSearchIndex = true;\n  });\n\n  //\n  // Send a signal to stdout that let's the build know we've reached this point\n  //\n  eleventyConfig.on('eleventy.after', () => {\n    console.log('[eleventy.after]');\n  });\n\n  //\n  // Dev server options (see https://www.11ty.dev/docs/dev-server/#options)\n  //\n  eleventyConfig.setServerOptions({\n    domDiff: false, // disable dom diffing so custom elements don't break on reload,\n    port: 4000, // if port 4000 is taken, 11ty will use the next one available\n    watch: ['cdn/**/*'] // additional files to watch that will trigger server updates (array of paths or globs)\n  });\n\n  //\n  // 11ty config\n  //\n  return {\n    dir: {\n      input: 'pages',\n      output: '../_site',\n      includes: '../_includes' // resolved relative to the input dir\n    },\n    markdownTemplateEngine: 'njk', // use Nunjucks instead of Liquid for markdown files\n    templateEngineOverride: ['njk'] // just Nunjucks and then markdown\n  };\n};\n"
  },
  {
    "path": "docs/pages/404.md",
    "content": "---\nmeta:\n  title: Page Not Found\n  description: \"The page you were looking for couldn't be found.\"\npermalink: 404.html\ntoc: false\n---\n\n<div style=\"text-align: center;\">\n\n# Page Not Found\n\n![A UFO takes one of the little worker monsters](/assets/images/undraw-taken.svg)\n\nThe page you were looking for couldn't be found.\n\nPress [[/]] to search, or [head back to the homepage](/).\n\n</div>\n"
  },
  {
    "path": "docs/pages/components/alert.md",
    "content": "---\nmeta:\n  title: Alert\n  description: Alerts are used to display important messages inline or as toast notifications.\nlayout: component\n---\n\n```html:preview\n<sl-alert open>\n  <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n  This is a standard alert. You can customize its content and even the icon.\n</sl-alert>\n```\n\n```jsx:react\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <SlAlert open>\n    <SlIcon slot=\"icon\" name=\"info-circle\" />\n    This is a standard alert. You can customize its content and even the icon.\n  </SlAlert>\n);\n```\n\n:::tip\nAlerts will not be visible if the `open` attribute is not present.\n:::\n\n## Examples\n\n### Variants\n\nSet the `variant` attribute to change the alert's variant.\n\n```html:preview\n<sl-alert variant=\"primary\" open>\n  <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n  <strong>This is super informative</strong><br />\n  You can tell by how pretty the alert is.\n</sl-alert>\n\n<br />\n\n<sl-alert variant=\"success\" open>\n  <sl-icon slot=\"icon\" name=\"check2-circle\"></sl-icon>\n  <strong>Your changes have been saved</strong><br />\n  You can safely exit the app now.\n</sl-alert>\n\n<br />\n\n<sl-alert variant=\"neutral\" open>\n  <sl-icon slot=\"icon\" name=\"gear\"></sl-icon>\n  <strong>Your settings have been updated</strong><br />\n  Settings will take effect on next login.\n</sl-alert>\n\n<br />\n\n<sl-alert variant=\"warning\" open>\n  <sl-icon slot=\"icon\" name=\"exclamation-triangle\"></sl-icon>\n  <strong>Your session has ended</strong><br />\n  Please login again to continue.\n</sl-alert>\n\n<br />\n\n<sl-alert variant=\"danger\" open>\n  <sl-icon slot=\"icon\" name=\"exclamation-octagon\"></sl-icon>\n  <strong>Your account has been deleted</strong><br />\n  We're very sorry to see you go!\n</sl-alert>\n```\n\n```jsx:react\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlAlert variant=\"primary\" open>\n      <SlIcon slot=\"icon\" name=\"info-circle\" />\n      <strong>This is super informative</strong>\n      <br />\n      You can tell by how pretty the alert is.\n    </SlAlert>\n\n    <br />\n\n    <SlAlert variant=\"success\" open>\n      <SlIcon slot=\"icon\" name=\"check2-circle\" />\n      <strong>Your changes have been saved</strong>\n      <br />\n      You can safely exit the app now.\n    </SlAlert>\n\n    <br />\n\n    <SlAlert variant=\"neutral\" open>\n      <SlIcon slot=\"icon\" name=\"gear\" />\n      <strong>Your settings have been updated</strong>\n      <br />\n      Settings will take effect on next login.\n    </SlAlert>\n\n    <br />\n\n    <SlAlert variant=\"warning\" open>\n      <SlIcon slot=\"icon\" name=\"exclamation-triangle\" />\n      <strong>Your session has ended</strong>\n      <br />\n      Please login again to continue.\n    </SlAlert>\n\n    <br />\n\n    <SlAlert variant=\"danger\" open>\n      <SlIcon slot=\"icon\" name=\"exclamation-octagon\" />\n      <strong>Your account has been deleted</strong>\n      <br />\n      We're very sorry to see you go!\n    </SlAlert>\n  </>\n);\n```\n\n### Closable\n\nAdd the `closable` attribute to show a close button that will hide the alert.\n\n```html:preview\n<sl-alert variant=\"primary\" open closable class=\"alert-closable\">\n  <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n  You can close this alert any time!\n</sl-alert>\n\n<script>\n  const alert = document.querySelector('.alert-closable');\n  alert.addEventListener('sl-after-hide', () => {\n    setTimeout(() => (alert.open = true), 2000);\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => {\n  const [open, setOpen] = useState(true);\n\n  function handleHide() {\n    setOpen(false);\n    setTimeout(() => setOpen(true), 2000);\n  }\n\n  return (\n    <SlAlert open={open} closable onSlAfterHide={handleHide}>\n      <SlIcon slot=\"icon\" name=\"info-circle\" />\n      You can close this alert any time!\n    </SlAlert>\n  );\n};\n```\n\n### Without Icons\n\nIcons are optional. Simply omit the `icon` slot if you don't want them.\n\n```html:preview\n<sl-alert variant=\"primary\" open> Nothing fancy here, just a simple alert. </sl-alert>\n```\n\n```jsx:react\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\n\nconst App = () => (\n  <SlAlert variant=\"primary\" open>\n    Nothing fancy here, just a simple alert.\n  </SlAlert>\n);\n```\n\n### Duration\n\nSet the `duration` attribute to automatically hide an alert after a period of time. This is useful for alerts that don't require acknowledgement.\n\n```html:preview\n<div class=\"alert-duration\">\n  <sl-button variant=\"primary\">Show Alert</sl-button>\n\n  <sl-alert variant=\"primary\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n    This alert will automatically hide itself after three seconds, unless you interact with it.\n  </sl-alert>\n</div>\n\n<script>\n  const container = document.querySelector('.alert-duration');\n  const button = container.querySelector('sl-button');\n  const alert = container.querySelector('sl-alert');\n\n  button.addEventListener('click', () => alert.show());\n</script>\n\n<style>\n  .alert-duration sl-alert {\n    margin-top: var(--sl-spacing-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst css = `\n  .alert-duration sl-alert {\n    margin-top: var(--sl-spacing-medium);\n  }\n`;\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <div className=\"alert-duration\">\n        <SlButton variant=\"primary\" onClick={() => setOpen(true)}>\n          Show Alert\n        </SlButton>\n\n        <SlAlert variant=\"primary\" duration=\"3000\" open={open} closable onSlAfterHide={() => setOpen(false)}>\n          <SlIcon slot=\"icon\" name=\"info-circle\" />\n          This alert will automatically hide itself after three seconds, unless you interact with it.\n        </SlAlert>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Countdown\n\nSet the `countdown` attribute to display a loading bar that indicates the alert remaining time. This is useful for alerts with relatively long duration.\n\n```html:preview\n<div class=\"alert-countdown\">\n  <sl-button variant=\"primary\">Show Alert</sl-button>\n\n  <sl-alert variant=\"primary\" duration=\"10000\" countdown=\"rtl\" closable>\n    <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n    You're not stuck, the alert will close after a pretty long duration.\n  </sl-alert>\n</div>\n\n<script>\n  const container = document.querySelector('.alert-countdown');\n  const button = container.querySelector('sl-button');\n  const alert = container.querySelector('sl-alert');\n\n  button.addEventListener('click', () => alert.show());\n</script>\n\n<style>\n  .alert-countdown sl-alert {\n    margin-top: var(--sl-spacing-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst css = `\n  .alert-countdown sl-alert {\n    margin-top: var(--sl-spacing-medium);\n  }\n`;\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <div className=\"alert-countdown\">\n        <SlButton variant=\"primary\" onClick={() => setOpen(true)}>\n          Show Alert\n        </SlButton>\n\n        <SlAlert variant=\"primary\" duration=\"3000\" countdown=\"rtl\" open={open} closable onSlAfterHide={() => setOpen(false)}>\n          <SlIcon slot=\"icon\" name=\"info-circle\" />\n          You're not stuck, the alert will close after a pretty long duration.\n        </SlAlert>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Toast Notifications\n\nTo display an alert as a toast notification, or \"toast\", create the alert and call its `toast()` method. This will move the alert out of its position in the DOM and into [the toast stack](#the-toast-stack) where it will be shown. Once dismissed, it will be removed from the DOM completely. To reuse a toast, store a reference to it and call `toast()` again later on.\n\nYou should always use the `closable` attribute so users can dismiss the notification. It's also common to set a reasonable `duration` when the notification doesn't require acknowledgement.\n\n```html:preview\n<div class=\"alert-toast\">\n  <sl-button variant=\"primary\">Primary</sl-button>\n  <sl-button variant=\"success\">Success</sl-button>\n  <sl-button variant=\"neutral\">Neutral</sl-button>\n  <sl-button variant=\"warning\">Warning</sl-button>\n  <sl-button variant=\"danger\">Danger</sl-button>\n\n  <sl-alert variant=\"primary\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n    <strong>This is super informative</strong><br />\n    You can tell by how pretty the alert is.\n  </sl-alert>\n\n  <sl-alert variant=\"success\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"check2-circle\"></sl-icon>\n    <strong>Your changes have been saved</strong><br />\n    You can safely exit the app now.\n  </sl-alert>\n\n  <sl-alert variant=\"neutral\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"gear\"></sl-icon>\n    <strong>Your settings have been updated</strong><br />\n    Settings will take effect on next login.\n  </sl-alert>\n\n  <sl-alert variant=\"warning\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"exclamation-triangle\"></sl-icon>\n    <strong>Your session has ended</strong><br />\n    Please login again to continue.\n  </sl-alert>\n\n  <sl-alert variant=\"danger\" duration=\"3000\" closable>\n    <sl-icon slot=\"icon\" name=\"exclamation-octagon\"></sl-icon>\n    <strong>Your account has been deleted</strong><br />\n    We're very sorry to see you go!\n  </sl-alert>\n</div>\n\n<script>\n  const container = document.querySelector('.alert-toast');\n\n  ['primary', 'success', 'neutral', 'warning', 'danger'].map(variant => {\n    const button = container.querySelector(`sl-button[variant=\"${variant}\"]`);\n    const alert = container.querySelector(`sl-alert[variant=\"${variant}\"]`);\n\n    button.addEventListener('click', () => alert.toast());\n  });\n</script>\n```\n\n```jsx:react\nimport { useRef } from 'react';\nimport SlAlert from '@shoelace-style/shoelace/dist/react/alert';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nfunction showToast(alert) {\n  alert.toast();\n}\n\nconst App = () => {\n  const primary = useRef(null);\n  const success = useRef(null);\n  const neutral = useRef(null);\n  const warning = useRef(null);\n  const danger = useRef(null);\n\n  return (\n    <>\n      <SlButton variant=\"primary\" onClick={() => primary.current.toast()}>\n        Primary\n      </SlButton>\n\n      <SlButton variant=\"success\" onClick={() => success.current.toast()}>\n        Success\n      </SlButton>\n\n      <SlButton variant=\"neutral\" onClick={() => neutral.current.toast()}>\n        Neutral\n      </SlButton>\n\n      <SlButton variant=\"warning\" onClick={() => warning.current.toast()}>\n        Warning\n      </SlButton>\n\n      <SlButton variant=\"danger\" onClick={() => danger.current.toast()}>\n        Danger\n      </SlButton>\n\n      <SlAlert ref={primary} variant=\"primary\" duration=\"3000\" closable>\n        <SlIcon slot=\"icon\" name=\"info-circle\" />\n        <strong>This is super informative</strong>\n        <br />\n        You can tell by how pretty the alert is.\n      </SlAlert>\n\n      <SlAlert ref={success} variant=\"success\" duration=\"3000\" closable>\n        <SlIcon slot=\"icon\" name=\"check2-circle\" />\n        <strong>Your changes have been saved</strong>\n        <br />\n        You can safely exit the app now.\n      </SlAlert>\n\n      <SlAlert ref={neutral} variant=\"neutral\" duration=\"3000\" closable>\n        <SlIcon slot=\"icon\" name=\"gear\" />\n        <strong>Your settings have been updated</strong>\n        <br />\n        Settings will take effect on next login.\n      </SlAlert>\n\n      <SlAlert ref={warning} variant=\"warning\" duration=\"3000\" closable>\n        <SlIcon slot=\"icon\" name=\"exclamation-triangle\" />\n        <strong>Your session has ended</strong>\n        <br />\n        Please login again to continue.\n      </SlAlert>\n\n      <SlAlert ref={danger} variant=\"danger\" duration=\"3000\" closable>\n        <SlIcon slot=\"icon\" name=\"exclamation-octagon\" />\n        <strong>Your account has been deleted</strong>\n        <br />\n        We're very sorry to see you go!\n      </SlAlert>\n    </>\n  );\n};\n```\n\n### Creating Toasts Imperatively\n\nFor convenience, you can create a utility that emits toast notifications with a function call rather than composing them in your HTML. To do this, generate the alert with JavaScript, append it to the body, and call the `toast()` method as shown in the example below.\n\n```html:preview\n<div class=\"alert-toast-wrapper\">\n  <sl-button variant=\"primary\">Create Toast</sl-button>\n</div>\n\n<script>\n  const container = document.querySelector('.alert-toast-wrapper');\n  const button = container.querySelector('sl-button');\n  let count = 0;\n\n  // Always escape HTML for text arguments!\n  function escapeHtml(html) {\n    const div = document.createElement('div');\n    div.textContent = html;\n    return div.innerHTML;\n  }\n\n  // Custom function to emit toast notifications\n  function notify(message, variant = 'primary', icon = 'info-circle', duration = 3000) {\n    const alert = Object.assign(document.createElement('sl-alert'), {\n      variant,\n      closable: true,\n      duration: duration,\n      innerHTML: `\n        <sl-icon name=\"${icon}\" slot=\"icon\"></sl-icon>\n        ${escapeHtml(message)}\n      `\n    });\n\n    document.body.append(alert);\n    return alert.toast();\n  }\n\n  button.addEventListener('click', () => {\n    notify(`This is custom toast #${++count}`);\n  });\n</script>\n```\n\n### The Toast Stack\n\nThe toast stack is a fixed position singleton element created and managed internally by the alert component. It will be added and removed from the DOM as needed when toasts are shown. When more than one toast is visible, they will stack vertically in the toast stack.\n\nBy default, the toast stack is positioned at the top-right of the viewport. You can change its position by targeting `.sl-toast-stack` in your stylesheet. To make toasts appear at the top-left of the viewport, for example, use the following styles.\n\n```css\n.sl-toast-stack {\n  left: 0;\n  right: auto;\n}\n```\n\n:::tip\nBy design, it is not possible to show toasts in more than one stack simultaneously. Such behavior is confusing and makes for a poor user experience.\n:::\n"
  },
  {
    "path": "docs/pages/components/animated-image.md",
    "content": "---\nmeta:\n  title: Animated Image\n  description: A component for displaying animated GIFs and WEBPs that play and pause on interaction.\nlayout: component\n---\n\n```html:preview\n<sl-animated-image\n  src=\"https://shoelace.style/assets/images/walk.gif\"\n  alt=\"Animation of untied shoes walking on pavement\"\n></sl-animated-image>\n```\n\n```jsx:react\nimport SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';\n\nconst App = () => (\n  <SlAnimatedImage\n    src=\"https://shoelace.style/assets/images/walk.gif\"\n    alt=\"Animation of untied shoes walking on pavement\"\n  />\n);\n```\n\n:::tip\nThis component uses `<canvas>` to draw freeze frames, so images are subject to [cross-origin restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).\n:::\n\n## Examples\n\n### WEBP Images\n\nBoth GIF and WEBP images are supported.\n\n```html:preview\n<sl-animated-image\n  src=\"https://shoelace.style/assets/images/tie.webp\"\n  alt=\"Animation of a shoe being tied\"\n></sl-animated-image>\n```\n\n```jsx:react\nimport SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';\n\nconst App = () => (\n  <SlAnimatedImage src=\"https://shoelace.style/assets/images/tie.webp\" alt=\"Animation of a shoe being tied\" />\n);\n```\n\n### Setting a Width and Height\n\nTo set a custom size, apply a width and/or height to the host element.\n\n```html:preview\n<sl-animated-image\n  src=\"https://shoelace.style/assets/images/walk.gif\"\n  alt=\"Animation of untied shoes walking on pavement\"\n  style=\"width: 150px; height: 200px;\"\n>\n</sl-animated-image>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';\n\nconst App = () => (\n  <SlAnimatedImage\n    src=\"https://shoelace.style/assets/images/walk.gif\"\n    alt=\"Animation of untied shoes walking on pavement\"\n    style={{ width: '150px', height: '200px' }}\n  />\n);\n```\n\n{% endraw %}\n\n### Customizing the Control Box\n\nYou can change the appearance and location of the control box by targeting the `control-box` part in your styles.\n\n```html:preview\n<sl-animated-image\n  src=\"https://shoelace.style/assets/images/walk.gif\"\n  alt=\"Animation of untied shoes walking on pavement\"\n  class=\"animated-image-custom-control-box\"\n></sl-animated-image>\n\n<style>\n  .animated-image-custom-control-box::part(control-box) {\n    top: auto;\n    right: auto;\n    bottom: 1rem;\n    left: 1rem;\n    background-color: deeppink;\n    border: none;\n    color: pink;\n  }\n</style>\n```\n\n```jsx:react\nimport SlAnimatedImage from '@shoelace-style/shoelace/dist/react/animated-image';\n\nconst css = `\n  .animated-image-custom-control-box::part(control-box) {\n    top: auto;\n    right: auto;\n    bottom: 1rem;\n    left: 1rem;\n    background-color: deeppink;\n    border: none;\n    color: pink;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlAnimatedImage\n      className=\"animated-image-custom-control-box\"\n      src=\"https://shoelace.style/assets/images/walk.gif\"\n      alt=\"Animation of untied shoes walking on pavement\"\n    />\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/animation.md",
    "content": "---\nmeta:\n  title: Animation\n  description: Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes.\nlayout: component\n---\n\nTo animate an element, wrap it in `<sl-animation>` and set an animation `name`. The animation will not start until you add the `play` attribute. Refer to the [properties table](#properties) for a list of all animation options.\n\n```html:preview\n<div class=\"animation-overview\">\n  <sl-animation name=\"bounce\" duration=\"2000\" play><div class=\"box\"></div></sl-animation>\n  <sl-animation name=\"jello\" duration=\"2000\" play><div class=\"box\"></div></sl-animation>\n  <sl-animation name=\"heartBeat\" duration=\"2000\" play><div class=\"box\"></div></sl-animation>\n  <sl-animation name=\"flip\" duration=\"2000\" play><div class=\"box\"></div></sl-animation>\n</div>\n\n<style>\n  .animation-overview .box {\n    display: inline-block;\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n    margin: 1.5rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlAnimation from '@shoelace-style/shoelace/dist/react/animation';\n\nconst css = `\n  .animation-overview .box {\n    display: inline-block;\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n    margin: 1.5rem;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div class=\"animation-overview\">\n      <SlAnimation name=\"bounce\" duration={2000} play>\n        <div class=\"box\" />\n      </SlAnimation>\n      <SlAnimation name=\"jello\" duration={2000} play>\n        <div class=\"box\" />\n      </SlAnimation>\n      <SlAnimation name=\"heartBeat\" duration={2000} play>\n        <div class=\"box\" />\n      </SlAnimation>\n      <SlAnimation name=\"flip\" duration={2000} play>\n        <div class=\"box\" />\n      </SlAnimation>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n:::tip\nThe animation will only be applied to the first child element found in `<sl-animation>`.\n:::\n\n## Examples\n\n### Animations & Easings\n\nThis example demonstrates all of the baked-in animations and easings. Animations are based on those found in the popular [Animate.css](https://animate.style/) library.\n\n```html:preview\n<div class=\"animation-sandbox\">\n  <sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"2000\" play>\n    <div class=\"box\"></div>\n  </sl-animation>\n\n  <div class=\"controls\">\n    <sl-select label=\"Animation\" value=\"bounce\"></sl-select>\n    <sl-select label=\"Easing\" value=\"linear\"></sl-select>\n    <sl-input label=\"Playback Rate\" type=\"number\" min=\"0\" max=\"2\" step=\".25\" value=\"1\"></sl-input>\n  </div>\n</div>\n\n<script type=\"module\">\n  import { getAnimationNames, getEasingNames } from '/dist/utilities/animation.js';\n\n  const container = document.querySelector('.animation-sandbox');\n  const animation = container.querySelector('sl-animation');\n  const animationName = container.querySelector('.controls sl-select:nth-child(1)');\n  const easingName = container.querySelector('.controls sl-select:nth-child(2)');\n  const playbackRate = container.querySelector('sl-input[type=\"number\"]');\n  const animations = getAnimationNames();\n  const easings = getEasingNames();\n\n  animations.map(name => {\n    const option = Object.assign(document.createElement('sl-option'), {\n      textContent: name,\n      value: name\n    });\n    animationName.appendChild(option);\n  });\n\n  easings.map(name => {\n    const option = Object.assign(document.createElement('sl-option'), {\n      textContent: name,\n      value: name\n    });\n    easingName.appendChild(option);\n  });\n\n  animationName.addEventListener('sl-change', () => (animation.name = animationName.value));\n  easingName.addEventListener('sl-change', () => (animation.easing = easingName.value));\n  playbackRate.addEventListener('sl-input', () => (animation.playbackRate = playbackRate.value));\n</script>\n\n<style>\n  .animation-sandbox .box {\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n  }\n\n  .animation-sandbox .controls {\n    max-width: 300px;\n    margin-top: 2rem;\n  }\n\n  .animation-sandbox .controls sl-select {\n    margin-bottom: 1rem;\n  }\n</style>\n```\n\n### Using Intersection Observer\n\nUse an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to control the animation when an element enters or exits the viewport. For example, scroll the box below in and out of your screen. The animation stops when the box exits the viewport and restarts each time it enters the viewport.\n\n```html:preview\n<div class=\"animation-scroll\">\n  <sl-animation name=\"jackInTheBox\" duration=\"2000\" iterations=\"1\"><div class=\"box\"></div></sl-animation>\n</div>\n\n<script>\n  const container = document.querySelector('.animation-scroll');\n  const animation = container.querySelector('sl-animation');\n  const box = animation.querySelector('.box');\n\n  // Watch for the box to enter and exit the viewport. Note that we're observing the box, not the animation element!\n  const observer = new IntersectionObserver(entries => {\n    if (entries[0].isIntersecting) {\n      // Start the animation when the box enters the viewport\n      animation.play = true;\n    } else {\n      animation.play = false;\n      animation.currentTime = 0;\n    }\n  });\n  observer.observe(box);\n</script>\n\n<style>\n  .animation-scroll .box {\n    display: inline-block;\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n  }\n</style>\n```\n\n```jsx:react\nimport { useEffect, useRef, useState } from 'react';\nimport SlAnimation from '@shoelace-style/shoelace/dist/react/animation';\n\nconst css = `\n  .animation-scroll {\n    height: calc(100vh + 100px);\n  }\n\n  .animation-scroll .box {\n    display: inline-block;\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n  }\n`;\n\nconst App = () => {\n  const animation = useRef(null);\n  const box = useRef(null);\n\n  useEffect(() => {\n    const observer = new IntersectionObserver(entries => {\n      if (entries[0].isIntersecting) {\n        animation.current.play = true;\n      } else {\n        animation.current.play = false;\n        animation.current.currentTime = 0;\n      }\n    });\n\n    if (box.current) {\n      observer.observe(box.current);\n    }\n  }, [box]);\n\n  return (\n    <>\n      <div class=\"animation-scroll\">\n        <SlAnimation ref={animation} name=\"jackInTheBox\" duration={2000} iterations={1}>\n          <div ref={box} class=\"box\" />\n        </SlAnimation>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Custom Keyframe Formats\n\nSupply your own [keyframe formats](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats) to build custom animations.\n\n```html:preview\n<div class=\"animation-keyframes\">\n  <sl-animation easing=\"ease-in-out\" duration=\"2000\" play>\n    <div class=\"box\"></div>\n  </sl-animation>\n</div>\n\n<script>\n  const animation = document.querySelector('.animation-keyframes sl-animation');\n  animation.keyframes = [\n    {\n      offset: 0,\n      easing: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',\n      fillMode: 'both',\n      transformOrigin: 'center center',\n      transform: 'rotate(0)'\n    },\n    {\n      offset: 1,\n      easing: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',\n      fillMode: 'both',\n      transformOrigin: 'center center',\n      transform: 'rotate(90deg)'\n    }\n  ];\n</script>\n\n<style>\n  .animation-keyframes .box {\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n  }\n</style>\n```\n\n```jsx:react\nimport SlAnimation from '@shoelace-style/shoelace/dist/react/animation';\n\nconst css = `\n  .animation-keyframes .box {\n    width: 100px;\n    height: 100px;\n    background-color: var(--sl-color-primary-600);\n  }\n`;\n\nconst App = () => (\n  <>\n    <div class=\"animation-keyframes\">\n      <SlAnimation\n        easing=\"ease-in-out\"\n        duration={2000}\n        play\n        keyframes={[\n          {\n            offset: 0,\n            easing: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',\n            fillMode: 'both',\n            transformOrigin: 'center center',\n            transform: 'rotate(0)'\n          },\n          {\n            offset: 1,\n            easing: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',\n            fillMode: 'both',\n            transformOrigin: 'center center',\n            transform: 'rotate(90deg)'\n          }\n        ]}\n      >\n        <div class=\"box\" />\n      </SlAnimation>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Playing Animations on Demand\n\nAnimations won't play until you apply the `play` attribute. You can omit it initially, then apply it on demand such as after a user interaction. In this example, the button will animate once every time the button is clicked.\n\n```html:preview\n<div class=\"animation-form\">\n  <sl-animation name=\"rubberBand\" duration=\"1000\" iterations=\"1\">\n    <sl-button variant=\"primary\">Click me</sl-button>\n  </sl-animation>\n</div>\n\n<script>\n  const container = document.querySelector('.animation-form');\n  const animation = container.querySelector('sl-animation');\n  const button = container.querySelector('sl-button');\n\n  button.addEventListener('click', () => {\n    animation.play = true;\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlAnimation from '@shoelace-style/shoelace/dist/react/animation';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => {\n  const [play, setPlay] = useState(false);\n\n  return (\n    <div class=\"animation-form\">\n      <SlAnimation name=\"rubberBand\" duration={1000} iterations={1} play={play} onSlFinish={() => setPlay(false)}>\n        <SlButton variant=\"primary\" onClick={() => setPlay(true)}>\n          Click me\n        </SlButton>\n      </SlAnimation>\n    </div>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/avatar.md",
    "content": "---\nmeta:\n  title: Avatar\n  description: Avatars are used to represent a person or object.\nlayout: component\n---\n\nBy default, a generic icon will be shown. You can personalize avatars by adding custom icons, initials, and images. You should always provide a `label` for assistive devices.\n\n```html:preview\n<sl-avatar label=\"User avatar\"></sl-avatar>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\n\nconst App = () => <SlAvatar label=\"User avatar\" />;\n```\n\n## Examples\n\n### Images\n\nTo use an image for the avatar, set the `image` and `label` attributes. This will take priority and be shown over initials and icons.\nAvatar images can be lazily loaded by setting the `loading` attribute to `lazy`.\n\n```html:preview\n<sl-avatar\n  image=\"https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80\"\n  label=\"Avatar of a gray tabby kitten looking down\"\n></sl-avatar>\n<sl-avatar\n  image=\"https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80\"\n  label=\"Avatar of a white and grey kitten on grey textile\"\n  loading=\"lazy\"\n></sl-avatar>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\n\nconst App = () => (\n  <SlAvatar\n    image=\"https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80\"\n    label=\"Avatar of a gray tabby kitten looking down\"\n  />\n  <SlAvatar\n    image=\"https://images.unsplash.com/photo-1591871937573-74dbba515c4c?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80\"\n    label=\"Avatar of a white and grey kitten on grey textile\"\n    loading=\"lazy\"\n  />\n);\n```\n\n### Initials\n\nWhen you don't have an image to use, you can set the `initials` attribute to show something more personalized than an icon.\n\n```html:preview\n<sl-avatar initials=\"SL\" label=\"Avatar with initials: SL\"></sl-avatar>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\n\nconst App = () => <SlAvatar initials=\"SL\" label=\"Avatar with initials: SL\" />;\n```\n\n### Custom Icons\n\nWhen no image or initials are set, an icon will be shown. The default avatar shows a generic \"user\" icon, but you can customize this with the `icon` slot.\n\n```html:preview\n<sl-avatar label=\"Avatar with an image icon\">\n  <sl-icon slot=\"icon\" name=\"image\"></sl-icon>\n</sl-avatar>\n\n<sl-avatar label=\"Avatar with an archive icon\">\n  <sl-icon slot=\"icon\" name=\"archive\"></sl-icon>\n</sl-avatar>\n\n<sl-avatar label=\"Avatar with a briefcase icon\">\n  <sl-icon slot=\"icon\" name=\"briefcase\"></sl-icon>\n</sl-avatar>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlAvatar label=\"Avatar with an image icon\">\n      <SlIcon slot=\"icon\" name=\"image\" />\n    </SlAvatar>\n\n    <SlAvatar label=\"Avatar with an archive icon\">\n      <SlIcon slot=\"icon\" name=\"archive\" />\n    </SlAvatar>\n\n    <SlAvatar label=\"Avatar with a briefcase icon\">\n      <SlIcon slot=\"icon\" name=\"briefcase\" />\n    </SlAvatar>\n  </>\n);\n```\n\n### Shapes\n\nAvatars can be shaped using the `shape` attribute.\n\n```html:preview\n<sl-avatar shape=\"square\" label=\"Square avatar\"></sl-avatar>\n<sl-avatar shape=\"rounded\" label=\"Rounded avatar\"></sl-avatar>\n<sl-avatar shape=\"circle\" label=\"Circle avatar\"></sl-avatar>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlAvatar shape=\"square\" label=\"Square avatar\" />\n    <SlAvatar shape=\"rounded\" label=\"Rounded avatar\" />\n    <SlAvatar shape=\"circle\" label=\"Circle avatar\" />\n  </>\n);\n```\n\n### Avatar Groups\n\nYou can group avatars with a few lines of CSS.\n\n```html:preview\n<div class=\"avatar-group\">\n  <sl-avatar\n    image=\"https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right\"\n    label=\"Avatar 1 of 4\"\n  ></sl-avatar>\n\n  <sl-avatar\n    image=\"https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80\"\n    label=\"Avatar 2 of 4\"\n  ></sl-avatar>\n\n  <sl-avatar\n    image=\"https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80\"\n    label=\"Avatar 3 of 4\"\n  ></sl-avatar>\n\n  <sl-avatar\n    image=\"https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80\"\n    label=\"Avatar 4 of 4\"\n  ></sl-avatar>\n</div>\n\n<style>\n  .avatar-group sl-avatar:not(:first-of-type) {\n    margin-left: -1rem;\n  }\n\n  .avatar-group sl-avatar::part(base) {\n    border: solid 2px var(--sl-color-neutral-0);\n  }\n</style>\n```\n\n```jsx:react\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst css = `\n  .avatar-group sl-avatar:not(:first-of-type) {\n    margin-left: -1rem;\n  }\n\n  .avatar-group sl-avatar::part(base) {\n    border: solid 2px var(--sl-color-neutral-0);\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"avatar-group\">\n      <SlAvatar\n        image=\"https://images.unsplash.com/photo-1490150028299-bf57d78394e0?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80&crop=right\"\n        label=\"Avatar 1 of 4\"\n      />\n\n      <SlAvatar\n        image=\"https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80\"\n        label=\"Avatar 2 of 4\"\n      />\n\n      <SlAvatar\n        image=\"https://images.unsplash.com/photo-1456439663599-95b042d50252?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=left&q=80\"\n        label=\"Avatar 3 of 4\"\n      />\n\n      <SlAvatar\n        image=\"https://images.unsplash.com/flagged/photo-1554078875-e37cb8b0e27d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&crop=top&q=80\"\n        label=\"Avatar 4 of 4\"\n      />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/badge.md",
    "content": "---\nmeta:\n  title: Badge\n  description: Badges are used to draw attention and display statuses or counts.\nlayout: component\n---\n\n```html:preview\n<sl-badge>Badge</sl-badge>\n```\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\n\nconst App = () => <SlBadge>Badge</SlBadge>;\n```\n\n## Examples\n\n### Variants\n\nSet the `variant` attribute to change the badge's variant.\n\n```html:preview\n<sl-badge variant=\"primary\">Primary</sl-badge>\n<sl-badge variant=\"success\">Success</sl-badge>\n<sl-badge variant=\"neutral\">Neutral</sl-badge>\n<sl-badge variant=\"warning\">Warning</sl-badge>\n<sl-badge variant=\"danger\">Danger</sl-badge>\n```\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\n\nconst App = () => (\n  <>\n    <SlBadge variant=\"primary\">Primary</SlBadge>\n    <SlBadge variant=\"success\">Success</SlBadge>\n    <SlBadge variant=\"neutral\">Neutral</SlBadge>\n    <SlBadge variant=\"warning\">Warning</SlBadge>\n    <SlBadge variant=\"danger\">Danger</SlBadge>\n  </>\n);\n```\n\n### Pill Badges\n\nUse the `pill` attribute to give badges rounded edges.\n\n```html:preview\n<sl-badge variant=\"primary\" pill>Primary</sl-badge>\n<sl-badge variant=\"success\" pill>Success</sl-badge>\n<sl-badge variant=\"neutral\" pill>Neutral</sl-badge>\n<sl-badge variant=\"warning\" pill>Warning</sl-badge>\n<sl-badge variant=\"danger\" pill>Danger</sl-badge>\n```\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\n\nconst App = () => (\n  <>\n    <SlBadge variant=\"primary\" pill>\n      Primary\n    </SlBadge>\n    <SlBadge variant=\"success\" pill>\n      Success\n    </SlBadge>\n    <SlBadge variant=\"neutral\" pill>\n      Neutral\n    </SlBadge>\n    <SlBadge variant=\"warning\" pill>\n      Warning\n    </SlBadge>\n    <SlBadge variant=\"danger\" pill>\n      Danger\n    </SlBadge>\n  </>\n);\n```\n\n### Pulsating Badges\n\nUse the `pulse` attribute to draw attention to the badge with a subtle animation.\n\n```html:preview\n<div class=\"badge-pulse\">\n  <sl-badge variant=\"primary\" pill pulse>1</sl-badge>\n  <sl-badge variant=\"success\" pill pulse>1</sl-badge>\n  <sl-badge variant=\"neutral\" pill pulse>1</sl-badge>\n  <sl-badge variant=\"warning\" pill pulse>1</sl-badge>\n  <sl-badge variant=\"danger\" pill pulse>1</sl-badge>\n</div>\n\n<style>\n  .badge-pulse sl-badge:not(:last-of-type) {\n    margin-right: 1rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\n\nconst css = `\n  .badge-pulse sl-badge:not(:last-of-type) {\n    margin-right: 1rem;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"badge-pulse\">\n      <SlBadge variant=\"primary\" pill pulse>\n        1\n      </SlBadge>\n      <SlBadge variant=\"success\" pill pulse>\n        1\n      </SlBadge>\n      <SlBadge variant=\"neutral\" pill pulse>\n        1\n      </SlBadge>\n      <SlBadge variant=\"warning\" pill pulse>\n        1\n      </SlBadge>\n      <SlBadge variant=\"danger\" pill pulse>\n        1\n      </SlBadge>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### With Buttons\n\nOne of the most common use cases for badges is attaching them to buttons. To make this easier, badges will be automatically positioned at the top-right when they're a child of a button.\n\n```html:preview\n<sl-button>\n  Requests\n  <sl-badge pill>30</sl-badge>\n</sl-button>\n\n<sl-button style=\"margin-inline-start: 1rem;\">\n  Warnings\n  <sl-badge variant=\"warning\" pill>8</sl-badge>\n</sl-button>\n\n<sl-button style=\"margin-inline-start: 1rem;\">\n  Errors\n  <sl-badge variant=\"danger\" pill>6</sl-badge>\n</sl-button>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton>\n      Requests\n      <SlBadge pill>30</SlBadge>\n    </SlButton>\n\n    <SlButton style={{ marginInlineStart: '1rem' }}>\n      Warnings\n      <SlBadge variant=\"warning\" pill>\n        8\n      </SlBadge>\n    </SlButton>\n\n    <SlButton style={{ marginInlineStart: '1rem' }}>\n      Errors\n      <SlBadge variant=\"danger\" pill>\n        6\n      </SlBadge>\n    </SlButton>\n  </>\n);\n```\n\n{% endraw %}\n\n### With Menu Items\n\nWhen including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.\n\n```html:preview\n<sl-menu style=\"max-width: 240px;\">\n  <sl-menu-label>Messages</sl-menu-label>\n  <sl-menu-item>Comments <sl-badge slot=\"suffix\" variant=\"neutral\" pill>4</sl-badge></sl-menu-item>\n  <sl-menu-item>Replies <sl-badge slot=\"suffix\" variant=\"neutral\" pill>12</sl-badge></sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\nimport SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';\n\nconst App = () => (\n  <SlMenu\n    style={{\n      maxWidth: '240px',\n      border: 'solid 1px var(--sl-panel-border-color)',\n      borderRadius: 'var(--sl-border-radius-medium)'\n    }}\n  >\n    <SlMenuLabel>Messages</SlMenuLabel>\n    <SlMenuItem>\n      Comments\n      <SlBadge slot=\"suffix\" variant=\"neutral\" pill>\n        4\n      </SlBadge>\n    </SlMenuItem>\n    <SlMenuItem>\n      Replies\n      <SlBadge slot=\"suffix\" variant=\"neutral\" pill>\n        12\n      </SlBadge>\n    </SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/breadcrumb-item.md",
    "content": "---\nmeta:\n  title: Breadcrumb Item\n  description: Breadcrumb Items are used inside breadcrumbs to represent different links.\nlayout: component\n---\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>\n    <sl-icon slot=\"prefix\" name=\"house\"></sl-icon>\n    Home\n  </sl-breadcrumb-item>\n  <sl-breadcrumb-item>Clothing</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Shirts</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>\n      <SlIcon slot=\"prefix\" name=\"house\"></SlIcon>\n      Home\n    </SlBreadcrumbItem>\n    <SlBreadcrumbItem>Clothing</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Shirts</SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\n:::tip\nAdditional demonstrations can be found in the [breadcrumb examples](/components/breadcrumb).\n:::\n"
  },
  {
    "path": "docs/pages/components/breadcrumb.md",
    "content": "---\nmeta:\n  title: Breadcrumb\n  description: Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.\nlayout: component\n---\n\nBreadcrumbs are usually placed before a page's main content with the current page shown last to indicate the user's position in the navigation.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>Catalog</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Clothing</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Women's</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Shirts &amp; Tops</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>Catalog</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Clothing</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Women's</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Shirts &amp; Tops</SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\n## Examples\n\n### Breadcrumb Links\n\nBy default, breadcrumb items are rendered as buttons so you can use them to navigate single-page applications. In this case, you'll need to add event listeners to handle clicks.\n\nFor websites, you'll probably want to use links instead. You can make any breadcrumb item a link by applying an `href` attribute to it. Now, when the user activates it, they'll be taken to the corresponding page — no event listeners required.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item href=\"https://example.com/home\">Homepage</sl-breadcrumb-item>\n\n  <sl-breadcrumb-item href=\"https://example.com/home/services\">Our Services</sl-breadcrumb-item>\n\n  <sl-breadcrumb-item href=\"https://example.com/home/services/digital\">Digital Media</sl-breadcrumb-item>\n\n  <sl-breadcrumb-item href=\"https://example.com/home/services/digital/web-design\">Web Design</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem href=\"https://example.com/home\">Homepage</SlBreadcrumbItem>\n\n    <SlBreadcrumbItem href=\"https://example.com/home/services\">Our Services</SlBreadcrumbItem>\n\n    <SlBreadcrumbItem href=\"https://example.com/home/services/digital\">Digital Media</SlBreadcrumbItem>\n\n    <SlBreadcrumbItem href=\"https://example.com/home/services/digital/web-design\">Web Design</SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\n### Custom Separators\n\nUse the `separator` slot to change the separator that goes between breadcrumb items. Icons work well, but you can also use text or an image.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-icon name=\"dot\" slot=\"separator\"></sl-icon>\n  <sl-breadcrumb-item>First</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n</sl-breadcrumb>\n\n<br />\n\n<sl-breadcrumb>\n  <sl-icon name=\"arrow-right\" slot=\"separator\"></sl-icon>\n  <sl-breadcrumb-item>First</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n</sl-breadcrumb>\n\n<br />\n\n<sl-breadcrumb>\n  <span slot=\"separator\">/</span>\n  <sl-breadcrumb-item>First</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport '@shoelace-style/shoelace/dist/components/icon/icon.js';\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\n\nconst App = () => (\n  <>\n    <SlBreadcrumb>\n      <sl-icon name=\"dot\" slot=\"separator\" />\n      <SlBreadcrumbItem>First</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Second</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Third</SlBreadcrumbItem>\n    </SlBreadcrumb>\n\n    <br />\n\n    <SlBreadcrumb>\n      <sl-icon name=\"arrow-right\" slot=\"separator\" />\n      <SlBreadcrumbItem>First</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Second</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Third</SlBreadcrumbItem>\n    </SlBreadcrumb>\n\n    <br />\n\n    <SlBreadcrumb>\n      <span slot=\"separator\">/</span>\n      <SlBreadcrumbItem>First</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Second</SlBreadcrumbItem>\n      <SlBreadcrumbItem>Third</SlBreadcrumbItem>\n    </SlBreadcrumb>\n  </>\n);\n```\n\n### Prefixes\n\nUse the `prefix` slot to add content before any breadcrumb item.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>\n    <sl-icon slot=\"prefix\" name=\"house\"></sl-icon>\n    Home\n  </sl-breadcrumb-item>\n  <sl-breadcrumb-item>Articles</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Traveling</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>\n      <SlIcon slot=\"prefix\" name=\"house\" />\n      Home\n    </SlBreadcrumbItem>\n    <SlBreadcrumbItem>Articles</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Traveling</SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\n### Suffixes\n\nUse the `suffix` slot to add content after any breadcrumb item.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>Documents</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Policies</sl-breadcrumb-item>\n  <sl-breadcrumb-item>\n    Security\n    <sl-icon slot=\"suffix\" name=\"shield-lock\"></sl-icon>\n  </sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport SlBreadcrumb from '@shoelace-style/shoelace/dist/react/breadcrumb';\nimport SlBreadcrumbItem from '@shoelace-style/shoelace/dist/react/breadcrumb-item';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>Documents</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Policies</SlBreadcrumbItem>\n    <SlBreadcrumbItem>\n      Security\n      <SlIcon slot=\"suffix\" name=\"shield-lock\"></SlIcon>\n    </SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\n### With Dropdowns\n\nDropdown menus can be placed in the default slot to provide additional options.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>Homepage</sl-breadcrumb-item>\n  <sl-breadcrumb-item>\n    <sl-dropdown>\n      <sl-button slot=\"trigger\" size=\"small\" circle>\n        <sl-icon label=\"More options\" name=\"three-dots\"></sl-icon>\n      </sl-button>\n      <sl-menu>\n        <sl-menu-item type=\"checkbox\" checked>Web Design</sl-menu-item>\n        <sl-menu-item type=\"checkbox\">Web Development</sl-menu-item>\n        <sl-menu-item type=\"checkbox\">Marketing</sl-menu-item>\n      </sl-menu>\n    </sl-dropdown>\n  </sl-breadcrumb-item>\n  <sl-breadcrumb-item>Our Services</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Digital Media</sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport {\n  SlBreadcrumb,\n  SlBreadcrumbItem,\n  SlButton,\n  SlDropdown,\n  SlIcon,\n  SlMenu,\n  SlMenuItem\n} from '@shoelace-style/shoelace/dist/react';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>Homepage</SlBreadcrumbItem>\n    <SlBreadcrumbItem>\n      <SlDropdown slot=\"suffix\">\n        <SlButton slot=\"trigger\" size=\"small\" circle>\n          <SlIcon label=\"More options\" name=\"three-dots\"></SlIcon>\n        </SlButton>\n        <SlMenu>\n          <SlMenuItem type=\"checkbox\" checked>\n            Web Design\n          </SlMenuItem>\n          <SlMenuItem type=\"checkbox\">Web Development</SlMenuItem>\n          <SlMenuItem type=\"checkbox\">Marketing</SlMenuItem>\n        </SlMenu>\n      </SlDropdown>\n    </SlBreadcrumbItem>\n    <SlBreadcrumbItem>Our Services</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Digital Media</SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n\nAlternatively, you can place dropdown menus in a prefix or suffix slot.\n\n```html:preview\n<sl-breadcrumb>\n  <sl-breadcrumb-item>Homepage</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Our Services</sl-breadcrumb-item>\n  <sl-breadcrumb-item>Digital Media</sl-breadcrumb-item>\n  <sl-breadcrumb-item>\n    Web Design\n    <sl-dropdown slot=\"suffix\">\n      <sl-button slot=\"trigger\" size=\"small\" circle>\n        <sl-icon label=\"More options\" name=\"three-dots\"></sl-icon>\n      </sl-button>\n      <sl-menu>\n        <sl-menu-item type=\"checkbox\" checked>Web Design</sl-menu-item>\n        <sl-menu-item type=\"checkbox\">Web Development</sl-menu-item>\n        <sl-menu-item type=\"checkbox\">Marketing</sl-menu-item>\n      </sl-menu>\n    </sl-dropdown>\n  </sl-breadcrumb-item>\n</sl-breadcrumb>\n```\n\n```jsx:react\nimport {\n  SlBreadcrumb,\n  SlBreadcrumbItem,\n  SlButton,\n  SlDropdown,\n  SlIcon,\n  SlMenu,\n  SlMenuItem\n} from '@shoelace-style/shoelace/dist/react';\n\nconst App = () => (\n  <SlBreadcrumb>\n    <SlBreadcrumbItem>Homepage</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Our Services</SlBreadcrumbItem>\n    <SlBreadcrumbItem>Digital Media</SlBreadcrumbItem>\n    <SlBreadcrumbItem>\n      Web Design\n      <SlDropdown slot=\"suffix\">\n        <SlButton slot=\"trigger\" size=\"small\" circle>\n          <SlIcon label=\"More options\" name=\"three-dots\"></SlIcon>\n        </SlButton>\n        <SlMenu>\n          <SlMenuItem type=\"checkbox\" checked>\n            Web Design\n          </SlMenuItem>\n          <SlMenuItem type=\"checkbox\">Web Development</SlMenuItem>\n          <SlMenuItem type=\"checkbox\">Marketing</SlMenuItem>\n        </SlMenu>\n      </SlDropdown>\n    </SlBreadcrumbItem>\n  </SlBreadcrumb>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/button-group.md",
    "content": "---\nmeta:\n  title: Button Group\n  description: Button groups can be used to group related buttons into sections.\nlayout: component\n---\n\n```html:preview\n<sl-button-group label=\"Alignment\">\n  <sl-button>Left</sl-button>\n  <sl-button>Center</sl-button>\n  <sl-button>Right</sl-button>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\n\nconst App = () => (\n  <SlButtonGroup label=\"Alignment\">\n    <SlButton>Left</SlButton>\n    <SlButton>Center</SlButton>\n    <SlButton>Right</SlButton>\n  </SlButtonGroup>\n);\n```\n\n## Examples\n\n### Button Sizes\n\nAll button sizes are supported, but avoid mixing sizes within the same button group.\n\n```html:preview\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"small\">Left</sl-button>\n  <sl-button size=\"small\">Center</sl-button>\n  <sl-button size=\"small\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"medium\">Left</sl-button>\n  <sl-button size=\"medium\">Center</sl-button>\n  <sl-button size=\"medium\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"large\">Left</sl-button>\n  <sl-button size=\"large\">Center</sl-button>\n  <sl-button size=\"large\">Right</sl-button>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\n\nconst App = () => (\n  <>\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"small\">Left</SlButton>\n      <SlButton size=\"small\">Center</SlButton>\n      <SlButton size=\"small\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"medium\">Left</SlButton>\n      <SlButton size=\"medium\">Center</SlButton>\n      <SlButton size=\"medium\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"large\">Left</SlButton>\n      <SlButton size=\"large\">Center</SlButton>\n      <SlButton size=\"large\">Right</SlButton>\n    </SlButtonGroup>\n  </>\n);\n```\n\n### Theme Buttons\n\nTheme buttons are supported through the button's `variant` attribute.\n\n```html:preview\n<sl-button-group label=\"Alignment\">\n  <sl-button variant=\"primary\">Left</sl-button>\n  <sl-button variant=\"primary\">Center</sl-button>\n  <sl-button variant=\"primary\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button variant=\"success\">Left</sl-button>\n  <sl-button variant=\"success\">Center</sl-button>\n  <sl-button variant=\"success\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button variant=\"neutral\">Left</sl-button>\n  <sl-button variant=\"neutral\">Center</sl-button>\n  <sl-button variant=\"neutral\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button variant=\"warning\">Left</sl-button>\n  <sl-button variant=\"warning\">Center</sl-button>\n  <sl-button variant=\"warning\">Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button variant=\"danger\">Left</sl-button>\n  <sl-button variant=\"danger\">Center</sl-button>\n  <sl-button variant=\"danger\">Right</sl-button>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\n\nconst App = () => (\n  <>\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton variant=\"primary\">Left</SlButton>\n      <SlButton variant=\"primary\">Center</SlButton>\n      <SlButton variant=\"primary\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton variant=\"success\">Left</SlButton>\n      <SlButton variant=\"success\">Center</SlButton>\n      <SlButton variant=\"success\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton variant=\"neutral\">Left</SlButton>\n      <SlButton variant=\"neutral\">Center</SlButton>\n      <SlButton variant=\"neutral\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton variant=\"warning\">Left</SlButton>\n      <SlButton variant=\"warning\">Center</SlButton>\n      <SlButton variant=\"warning\">Right</SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton variant=\"danger\">Left</SlButton>\n      <SlButton variant=\"danger\">Center</SlButton>\n      <SlButton variant=\"danger\">Right</SlButton>\n    </SlButtonGroup>\n  </>\n);\n```\n\n### Pill Buttons\n\nPill buttons are supported through the button's `pill` attribute.\n\n```html:preview\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"small\" pill>Left</sl-button>\n  <sl-button size=\"small\" pill>Center</sl-button>\n  <sl-button size=\"small\" pill>Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"medium\" pill>Left</sl-button>\n  <sl-button size=\"medium\" pill>Center</sl-button>\n  <sl-button size=\"medium\" pill>Right</sl-button>\n</sl-button-group>\n\n<br /><br />\n\n<sl-button-group label=\"Alignment\">\n  <sl-button size=\"large\" pill>Left</sl-button>\n  <sl-button size=\"large\" pill>Center</sl-button>\n  <sl-button size=\"large\" pill>Right</sl-button>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\n\nconst App = () => (\n  <>\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"small\" pill>\n        Left\n      </SlButton>\n      <SlButton size=\"small\" pill>\n        Center\n      </SlButton>\n      <SlButton size=\"small\" pill>\n        Right\n      </SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"medium\" pill>\n        Left\n      </SlButton>\n      <SlButton size=\"medium\" pill>\n        Center\n      </SlButton>\n      <SlButton size=\"medium\" pill>\n        Right\n      </SlButton>\n    </SlButtonGroup>\n\n    <br />\n    <br />\n\n    <SlButtonGroup label=\"Alignment\">\n      <SlButton size=\"large\" pill>\n        Left\n      </SlButton>\n      <SlButton size=\"large\" pill>\n        Center\n      </SlButton>\n      <SlButton size=\"large\" pill>\n        Right\n      </SlButton>\n    </SlButtonGroup>\n  </>\n);\n```\n\n### Dropdowns in Button Groups\n\nDropdowns can be placed inside button groups as long as the trigger is an `<sl-button>` element.\n\n```html:preview\n<sl-button-group label=\"Example Button Group\">\n  <sl-button>Button</sl-button>\n  <sl-button>Button</sl-button>\n  <sl-dropdown>\n    <sl-button slot=\"trigger\" caret>Dropdown</sl-button>\n    <sl-menu>\n      <sl-menu-item>Item 1</sl-menu-item>\n      <sl-menu-item>Item 2</sl-menu-item>\n      <sl-menu-item>Item 3</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlButtonGroup label=\"Example Button Group\">\n    <SlButton>Button</SlButton>\n    <SlButton>Button</SlButton>\n    <SlDropdown>\n      <SlButton slot=\"trigger\" caret>\n        Dropdown\n      </SlButton>\n      <SlMenu>\n        <SlMenuItem>Item 1</SlMenuItem>\n        <SlMenuItem>Item 2</SlMenuItem>\n        <SlMenuItem>Item 3</SlMenuItem>\n      </SlMenu>\n    </SlDropdown>\n  </SlButtonGroup>\n);\n```\n\n### Split Buttons\n\nCreate a split button using a button and a dropdown. Use a [visually hidden](/components/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices.\n\n```html:preview\n<sl-button-group label=\"Example Button Group\">\n  <sl-button variant=\"primary\">Save</sl-button>\n  <sl-dropdown placement=\"bottom-end\">\n    <sl-button slot=\"trigger\" variant=\"primary\" caret>\n      <sl-visually-hidden>More options</sl-visually-hidden>\n    </sl-button>\n    <sl-menu>\n      <sl-menu-item>Save</sl-menu-item>\n      <sl-menu-item>Save as&hellip;</sl-menu-item>\n      <sl-menu-item>Save all</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlButtonGroup label=\"Example Button Group\">\n    <SlButton variant=\"primary\">Save</SlButton>\n    <SlDropdown placement=\"bottom-end\">\n      <SlButton slot=\"trigger\" variant=\"primary\" caret></SlButton>\n      <SlMenu>\n        <SlMenuItem>Save</SlMenuItem>\n        <SlMenuItem>Save as&hellip;</SlMenuItem>\n        <SlMenuItem>Save all</SlMenuItem>\n      </SlMenu>\n    </SlDropdown>\n  </SlButtonGroup>\n);\n```\n\n### Tooltips in Button Groups\n\nButtons can be wrapped in tooltips to provide more detail when the user interacts with them.\n\n```html:preview\n<sl-button-group label=\"Alignment\">\n  <sl-tooltip content=\"I'm on the left\">\n    <sl-button>Left</sl-button>\n  </sl-tooltip>\n\n  <sl-tooltip content=\"I'm in the middle\">\n    <sl-button>Center</sl-button>\n  </sl-tooltip>\n\n  <sl-tooltip content=\"I'm on the right\">\n    <sl-button>Right</sl-button>\n  </sl-tooltip>\n</sl-button-group>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <>\n    <SlButtonGroup label=\"Alignment\">\n      <SlTooltip content=\"I'm on the left\">\n        <SlButton>Left</SlButton>\n      </SlTooltip>\n\n      <SlTooltip content=\"I'm in the middle\">\n        <SlButton>Center</SlButton>\n      </SlTooltip>\n\n      <SlTooltip content=\"I'm on the right\">\n        <SlButton>Right</SlButton>\n      </SlTooltip>\n    </SlButtonGroup>\n  </>\n);\n```\n\n### Toolbar Example\n\nCreate interactive toolbars with button groups.\n\n```html:preview\n<div class=\"button-group-toolbar\">\n  <sl-button-group label=\"History\">\n    <sl-tooltip content=\"Undo\">\n      <sl-button><sl-icon name=\"arrow-counterclockwise\" label=\"Undo\"></sl-icon></sl-button>\n    </sl-tooltip>\n    <sl-tooltip content=\"Redo\">\n      <sl-button><sl-icon name=\"arrow-clockwise\" label=\"Redo\"></sl-icon></sl-button>\n    </sl-tooltip>\n  </sl-button-group>\n\n  <sl-button-group label=\"Formatting\">\n    <sl-tooltip content=\"Bold\">\n      <sl-button><sl-icon name=\"type-bold\" label=\"Bold\"></sl-icon></sl-button>\n    </sl-tooltip>\n    <sl-tooltip content=\"Italic\">\n      <sl-button><sl-icon name=\"type-italic\" label=\"Italic\"></sl-icon></sl-button>\n    </sl-tooltip>\n    <sl-tooltip content=\"Underline\">\n      <sl-button><sl-icon name=\"type-underline\" label=\"Underline\"></sl-icon></sl-button>\n    </sl-tooltip>\n  </sl-button-group>\n\n  <sl-button-group label=\"Alignment\">\n    <sl-tooltip content=\"Align Left\">\n      <sl-button><sl-icon name=\"justify-left\" label=\"Align Left\"></sl-icon></sl-button>\n    </sl-tooltip>\n    <sl-tooltip content=\"Align Center\">\n      <sl-button><sl-icon name=\"justify\" label=\"Align Center\"></sl-icon></sl-button>\n    </sl-tooltip>\n    <sl-tooltip content=\"Align Right\">\n      <sl-button><sl-icon name=\"justify-right\" label=\"Align Right\"></sl-icon></sl-button>\n    </sl-tooltip>\n  </sl-button-group>\n</div>\n\n<style>\n  .button-group-toolbar sl-button-group:not(:last-of-type) {\n    margin-right: var(--sl-spacing-x-small);\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlButtonGroup from '@shoelace-style/shoelace/dist/react/button-group';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst css = `\n  .button-group-toolbar sl-button-group:not(:last-of-type) {\n    margin-right: var(--sl-spacing-x-small);\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"button-group-toolbar\">\n      <SlButtonGroup label=\"History\">\n        <SlTooltip content=\"Undo\">\n          <SlButton>\n            <SlIcon name=\"arrow-counterclockwise\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n        <SlTooltip content=\"Redo\">\n          <SlButton>\n            <SlIcon name=\"arrow-clockwise\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n      </SlButtonGroup>\n\n      <SlButtonGroup label=\"Formatting\">\n        <SlTooltip content=\"Bold\">\n          <SlButton>\n            <SlIcon name=\"type-bold\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n        <SlTooltip content=\"Italic\">\n          <SlButton>\n            <SlIcon name=\"type-italic\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n        <SlTooltip content=\"Underline\">\n          <SlButton>\n            <SlIcon name=\"type-underline\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n      </SlButtonGroup>\n\n      <SlButtonGroup label=\"Alignment\">\n        <SlTooltip content=\"Align Left\">\n          <SlButton>\n            <SlIcon name=\"justify-left\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n        <SlTooltip content=\"Align Center\">\n          <SlButton>\n            <SlIcon name=\"justify\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n        <SlTooltip content=\"Align Right\">\n          <SlButton>\n            <SlIcon name=\"justify-right\"></SlIcon>\n          </SlButton>\n        </SlTooltip>\n      </SlButtonGroup>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/button.md",
    "content": "---\nmeta:\n  title: Button\n  description: Buttons represent actions that are available to the user.\nlayout: component\n---\n\n```html:preview\n<sl-button>Button</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => <SlButton>Button</SlButton>;\n```\n\n## Examples\n\n### Variants\n\nUse the `variant` attribute to set the button's variant.\n\n```html:preview\n<sl-button variant=\"default\">Default</sl-button>\n<sl-button variant=\"primary\">Primary</sl-button>\n<sl-button variant=\"success\">Success</sl-button>\n<sl-button variant=\"neutral\">Neutral</sl-button>\n<sl-button variant=\"warning\">Warning</sl-button>\n<sl-button variant=\"danger\">Danger</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\">Default</SlButton>\n    <SlButton variant=\"primary\">Primary</SlButton>\n    <SlButton variant=\"success\">Success</SlButton>\n    <SlButton variant=\"neutral\">Neutral</SlButton>\n    <SlButton variant=\"warning\">Warning</SlButton>\n    <SlButton variant=\"danger\">Danger</SlButton>\n  </>\n);\n```\n\n### Sizes\n\nUse the `size` attribute to change a button's size.\n\n```html:preview\n<sl-button size=\"small\">Small</sl-button>\n<sl-button size=\"medium\">Medium</sl-button>\n<sl-button size=\"large\">Large</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton size=\"small\">Small</SlButton>\n    <SlButton size=\"medium\">Medium</SlButton>\n    <SlButton size=\"large\">Large</SlButton>\n  </>\n);\n```\n\n### Outline Buttons\n\nUse the `outline` attribute to draw outlined buttons with transparent backgrounds.\n\n```html:preview\n<sl-button variant=\"default\" outline>Default</sl-button>\n<sl-button variant=\"primary\" outline>Primary</sl-button>\n<sl-button variant=\"success\" outline>Success</sl-button>\n<sl-button variant=\"neutral\" outline>Neutral</sl-button>\n<sl-button variant=\"warning\" outline>Warning</sl-button>\n<sl-button variant=\"danger\" outline>Danger</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" outline>\n      Default\n    </SlButton>\n    <SlButton variant=\"primary\" outline>\n      Primary\n    </SlButton>\n    <SlButton variant=\"success\" outline>\n      Success\n    </SlButton>\n    <SlButton variant=\"neutral\" outline>\n      Neutral\n    </SlButton>\n    <SlButton variant=\"warning\" outline>\n      Warning\n    </SlButton>\n    <SlButton variant=\"danger\" outline>\n      Danger\n    </SlButton>\n  </>\n);\n```\n\n### Pill Buttons\n\nUse the `pill` attribute to give buttons rounded edges.\n\n```html:preview\n<sl-button size=\"small\" pill>Small</sl-button>\n<sl-button size=\"medium\" pill>Medium</sl-button>\n<sl-button size=\"large\" pill>Large</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton size=\"small\" pill>\n      Small\n    </SlButton>\n    <SlButton size=\"medium\" pill>\n      Medium\n    </SlButton>\n    <SlButton size=\"large\" pill>\n      Large\n    </SlButton>\n  </>\n);\n```\n\n### Circle Buttons\n\nUse the `circle` attribute to create circular icon buttons. When this attribute is set, the button expects a single `<sl-icon>` in the default slot.\n\n```html:preview\n<sl-button variant=\"default\" size=\"small\" circle>\n  <sl-icon name=\"gear\" label=\"Settings\"></sl-icon>\n</sl-button>\n\n<sl-button variant=\"default\" size=\"medium\" circle>\n  <sl-icon name=\"gear\" label=\"Settings\"></sl-icon>\n</sl-button>\n\n<sl-button variant=\"default\" size=\"large\" circle>\n  <sl-icon name=\"gear\" label=\"Settings\"></sl-icon>\n</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" size=\"small\" circle>\n      <SlIcon name=\"gear\" />\n    </SlButton>\n    <SlButton variant=\"default\" size=\"medium\" circle>\n      <SlIcon name=\"gear\" />\n    </SlButton>\n    <SlButton variant=\"default\" size=\"large\" circle>\n      <SlIcon name=\"gear\" />\n    </SlButton>\n  </>\n);\n```\n\n### Text Buttons\n\nUse the `text` variant to create text buttons that share the same size as regular buttons but don't have backgrounds or borders.\n\n```html:preview\n<sl-button variant=\"text\" size=\"small\">Text</sl-button>\n<sl-button variant=\"text\" size=\"medium\">Text</sl-button>\n<sl-button variant=\"text\" size=\"large\">Text</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"text\" size=\"small\">\n      Text\n    </SlButton>\n    <SlButton variant=\"text\" size=\"medium\">\n      Text\n    </SlButton>\n    <SlButton variant=\"text\" size=\"large\">\n      Text\n    </SlButton>\n  </>\n);\n```\n\n### Link Buttons\n\nIt's often helpful to have a button that works like a link. This is possible by setting the `href` attribute, which will make the component render an `<a>` under the hood. This gives you all the default link behavior the browser provides (e.g. [[CMD/CTRL/SHIFT]] + [[CLICK]]) and exposes the `target` and `download` attributes.\n\n```html:preview\n<sl-button href=\"https://example.com/\">Link</sl-button>\n<sl-button href=\"https://example.com/\" target=\"_blank\">New Window</sl-button>\n<sl-button href=\"/assets/images/wordmark.svg\" download=\"shoelace.svg\">Download</sl-button>\n<sl-button href=\"https://example.com/\" disabled>Disabled</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton href=\"https://example.com/\">Link</SlButton>\n    <SlButton href=\"https://example.com/\" target=\"_blank\">\n      New Window\n    </SlButton>\n    <SlButton href=\"/assets/images/wordmark.svg\" download=\"shoelace.svg\">\n      Download\n    </SlButton>\n    <SlButton href=\"https://example.com/\" disabled>\n      Disabled\n    </SlButton>\n  </>\n);\n```\n\n:::tip\nWhen a `target` is set, the link will receive `rel=\"noreferrer noopener\"` for [security reasons](https://mathiasbynens.github.io/rel-noopener/).\n:::\n\n### Setting a Custom Width\n\nAs expected, buttons can be given a custom width by passing inline styles to the component (or using a class). This is useful for making buttons span the full width of their container on smaller screens.\n\n```html:preview\n<sl-button variant=\"default\" size=\"small\" style=\"width: 100%; margin-bottom: 1rem;\">Small</sl-button>\n<sl-button variant=\"default\" size=\"medium\" style=\"width: 100%; margin-bottom: 1rem;\">Medium</sl-button>\n<sl-button variant=\"default\" size=\"large\" style=\"width: 100%;\">Large</sl-button>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" size=\"small\" style={{ width: '100%', marginBottom: '1rem' }}>\n      Small\n    </SlButton>\n    <SlButton variant=\"default\" size=\"medium\" style={{ width: '100%', marginBottom: '1rem' }}>\n      Medium\n    </SlButton>\n    <SlButton variant=\"default\" size=\"large\" style={{ width: '100%' }}>\n      Large\n    </SlButton>\n  </>\n);\n```\n\n{% endraw %}\n\n### Prefix and Suffix Icons\n\nUse the `prefix` and `suffix` slots to add icons.\n\n```html:preview\n<sl-button variant=\"default\" size=\"small\">\n  <sl-icon slot=\"prefix\" name=\"gear\"></sl-icon>\n  Settings\n</sl-button>\n\n<sl-button variant=\"default\" size=\"small\">\n  <sl-icon slot=\"suffix\" name=\"arrow-counterclockwise\"></sl-icon>\n  Refresh\n</sl-button>\n\n<sl-button variant=\"default\" size=\"small\">\n  <sl-icon slot=\"prefix\" name=\"link-45deg\"></sl-icon>\n  <sl-icon slot=\"suffix\" name=\"box-arrow-up-right\"></sl-icon>\n  Open\n</sl-button>\n\n<br /><br />\n\n<sl-button variant=\"default\">\n  <sl-icon slot=\"prefix\" name=\"gear\"></sl-icon>\n  Settings\n</sl-button>\n\n<sl-button variant=\"default\">\n  <sl-icon slot=\"suffix\" name=\"arrow-counterclockwise\"></sl-icon>\n  Refresh\n</sl-button>\n\n<sl-button variant=\"default\">\n  <sl-icon slot=\"prefix\" name=\"link-45deg\"></sl-icon>\n  <sl-icon slot=\"suffix\" name=\"box-arrow-up-right\"></sl-icon>\n  Open\n</sl-button>\n\n<br /><br />\n\n<sl-button variant=\"default\" size=\"large\">\n  <sl-icon slot=\"prefix\" name=\"gear\"></sl-icon>\n  Settings\n</sl-button>\n\n<sl-button variant=\"default\" size=\"large\">\n  <sl-icon slot=\"suffix\" name=\"arrow-counterclockwise\"></sl-icon>\n  Refresh\n</sl-button>\n\n<sl-button variant=\"default\" size=\"large\">\n  <sl-icon slot=\"prefix\" name=\"link-45deg\"></sl-icon>\n  <sl-icon slot=\"suffix\" name=\"box-arrow-up-right\"></sl-icon>\n  Open\n</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" size=\"small\">\n      <SlIcon slot=\"prefix\" name=\"gear\"></SlIcon>\n      Settings\n    </SlButton>\n\n    <SlButton variant=\"default\" size=\"small\">\n      <SlIcon slot=\"suffix\" name=\"arrow-counterclockwise\"></SlIcon>\n      Refresh\n    </SlButton>\n\n    <SlButton variant=\"default\" size=\"small\">\n      <SlIcon slot=\"prefix\" name=\"link-45deg\"></SlIcon>\n      <SlIcon slot=\"suffix\" name=\"box-arrow-up-right\"></SlIcon>\n      Open\n    </SlButton>\n\n    <br />\n    <br />\n\n    <SlButton variant=\"default\">\n      <SlIcon slot=\"prefix\" name=\"gear\"></SlIcon>\n      Settings\n    </SlButton>\n\n    <SlButton variant=\"default\">\n      <SlIcon slot=\"suffix\" name=\"arrow-counterclockwise\"></SlIcon>\n      Refresh\n    </SlButton>\n\n    <SlButton variant=\"default\">\n      <SlIcon slot=\"prefix\" name=\"link-45deg\"></SlIcon>\n      <SlIcon slot=\"suffix\" name=\"box-arrow-up-right\"></SlIcon>\n      Open\n    </SlButton>\n\n    <br />\n    <br />\n\n    <SlButton variant=\"default\" size=\"large\">\n      <SlIcon slot=\"prefix\" name=\"gear\"></SlIcon>\n      Settings\n    </SlButton>\n\n    <SlButton variant=\"default\" size=\"large\">\n      <SlIcon slot=\"suffix\" name=\"arrow-counterclockwise\"></SlIcon>\n      Refresh\n    </SlButton>\n\n    <SlButton variant=\"default\" size=\"large\">\n      <SlIcon slot=\"prefix\" name=\"link-45deg\"></SlIcon>\n      <SlIcon slot=\"suffix\" name=\"box-arrow-up-right\"></SlIcon>\n      Open\n    </SlButton>\n  </>\n);\n```\n\n### Caret\n\nUse the `caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.\n\n```html:preview\n<sl-button size=\"small\" caret>Small</sl-button>\n<sl-button size=\"medium\" caret>Medium</sl-button>\n<sl-button size=\"large\" caret>Large</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton size=\"small\" caret>\n      Small\n    </SlButton>\n    <SlButton size=\"medium\" caret>\n      Medium\n    </SlButton>\n    <SlButton size=\"large\" caret>\n      Large\n    </SlButton>\n  </>\n);\n```\n\n### Loading\n\nUse the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around.\n\n```html:preview\n<sl-button variant=\"default\" loading>Default</sl-button>\n<sl-button variant=\"primary\" loading>Primary</sl-button>\n<sl-button variant=\"success\" loading>Success</sl-button>\n<sl-button variant=\"neutral\" loading>Neutral</sl-button>\n<sl-button variant=\"warning\" loading>Warning</sl-button>\n<sl-button variant=\"danger\" loading>Danger</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" loading>\n      Default\n    </SlButton>\n    <SlButton variant=\"primary\" loading>\n      Primary\n    </SlButton>\n    <SlButton variant=\"success\" loading>\n      Success\n    </SlButton>\n    <SlButton variant=\"neutral\" loading>\n      Neutral\n    </SlButton>\n    <SlButton variant=\"warning\" loading>\n      Warning\n    </SlButton>\n    <SlButton variant=\"danger\" loading>\n      Danger\n    </SlButton>\n  </>\n);\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a button.\n\n```html:preview\n<sl-button variant=\"default\" disabled>Default</sl-button>\n<sl-button variant=\"primary\" disabled>Primary</sl-button>\n<sl-button variant=\"success\" disabled>Success</sl-button>\n<sl-button variant=\"neutral\" disabled>Neutral</sl-button>\n<sl-button variant=\"warning\" disabled>Warning</sl-button>\n<sl-button variant=\"danger\" disabled>Danger</sl-button>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\n\nconst App = () => (\n  <>\n    <SlButton variant=\"default\" disabled>\n      Default\n    </SlButton>\n\n    <SlButton variant=\"primary\" disabled>\n      Primary\n    </SlButton>\n\n    <SlButton variant=\"success\" disabled>\n      Success\n    </SlButton>\n\n    <SlButton variant=\"neutral\" disabled>\n      Neutral\n    </SlButton>\n\n    <SlButton variant=\"warning\" disabled>\n      Warning\n    </SlButton>\n\n    <SlButton variant=\"danger\" disabled>\n      Danger\n    </SlButton>\n  </>\n);\n```\n\n### Styling Buttons\n\nThis example demonstrates how to style buttons using a custom class. This is the recommended approach if you need to add additional variations. To customize an existing variation, modify the selector to target the button's `variant` attribute instead of a class (e.g. `sl-button[variant=\"primary\"]`).\n\n```html:preview\n<sl-button class=\"pink\">Pink Button</sl-button>\n\n<style>\n  sl-button.pink::part(base) {\n    /* Set design tokens for height and border width */\n    --sl-input-height-medium: 48px;\n    --sl-input-border-width: 4px;\n\n    border-radius: 0;\n    background-color: #ff1493;\n    border-top-color: #ff7ac1;\n    border-left-color: #ff7ac1;\n    border-bottom-color: #ad005c;\n    border-right-color: #ad005c;\n    color: white;\n    font-size: 1.125rem;\n    box-shadow: 0 2px 10px #0002;\n    transition: var(--sl-transition-medium) transform ease, var(--sl-transition-medium) border ease;\n  }\n\n  sl-button.pink::part(base):hover {\n    transform: scale(1.05) rotate(-1deg);\n  }\n\n  sl-button.pink::part(base):active {\n    border-top-color: #ad005c;\n    border-right-color: #ff7ac1;\n    border-bottom-color: #ff7ac1;\n    border-left-color: #ad005c;\n    transform: scale(1.05) rotate(-1deg) translateY(2px);\n  }\n\n  sl-button.pink::part(base):focus-visible {\n    outline: dashed 2px deeppink;\n    outline-offset: 4px;\n  }\n</style>\n```\n"
  },
  {
    "path": "docs/pages/components/card.md",
    "content": "---\nmeta:\n  title: Card\n  description: Cards can be used to group related subjects in a container.\nlayout: component\n---\n\n```html:preview\n<sl-card class=\"card-overview\">\n  <img\n    slot=\"image\"\n    src=\"https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80\"\n    alt=\"A kitten sits patiently between a terracotta pot and decorative grasses.\"\n  />\n\n  <strong>Mittens</strong><br />\n  This kitten is as cute as he is playful. Bring him home today!<br />\n  <small>6 weeks old</small>\n\n  <div slot=\"footer\">\n    <sl-button variant=\"primary\" pill>More Info</sl-button>\n    <sl-rating></sl-rating>\n  </div>\n</sl-card>\n\n<style>\n  .card-overview {\n    max-width: 300px;\n  }\n\n  .card-overview small {\n    color: var(--sl-color-neutral-500);\n  }\n\n  .card-overview [slot='footer'] {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlCard from '@shoelace-style/shoelace/dist/react/card';\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst css = `\n  .card-overview {\n    max-width: 300px;\n  }\n\n  .card-overview small {\n    color: var(--sl-color-neutral-500);\n  }\n\n  .card-overview [slot=\"footer\"] {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCard className=\"card-overview\">\n      <img\n        slot=\"image\"\n        src=\"https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80\"\n        alt=\"A kitten sits patiently between a terracotta pot and decorative grasses.\"\n      />\n      <strong>Mittens</strong>\n      <br />\n      This kitten is as cute as he is playful. Bring him home today!\n      <br />\n      <small>6 weeks old</small>\n      <div slot=\"footer\">\n        <SlButton variant=\"primary\" pill>\n          More Info\n        </SlButton>\n        <SlRating></SlRating>\n      </div>\n    </SlCard>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n## Examples\n\n### Basic Card\n\nBasic cards aren't very exciting, but they can display any content you want them to.\n\n```html:preview\n<sl-card class=\"card-basic\">\n  This is just a basic card. No image, no header, and no footer. Just your content.\n</sl-card>\n\n<style>\n  .card-basic {\n    max-width: 300px;\n  }\n</style>\n```\n\n```jsx:react\nimport SlCard from '@shoelace-style/shoelace/dist/react/card';\n\nconst css = `\n  .card-basic {\n    max-width: 300px;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCard className=\"card-basic\">\n      This is just a basic card. No image, no header, and no footer. Just your content.\n    </SlCard>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Card with Header\n\nHeaders can be used to display titles and more.\n\n```html:preview\n<sl-card class=\"card-header\">\n  <div slot=\"header\">\n    Header Title\n    <sl-icon-button name=\"gear\" label=\"Settings\"></sl-icon-button>\n  </div>\n\n  This card has a header. You can put all sorts of things in it!\n</sl-card>\n\n<style>\n  .card-header {\n    max-width: 300px;\n  }\n\n  .card-header [slot='header'] {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .card-header h3 {\n    margin: 0;\n  }\n\n  .card-header sl-icon-button {\n    font-size: var(--sl-font-size-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport SlCard from '@shoelace-style/shoelace/dist/react/card';\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst css = `\n  .card-header {\n    max-width: 300px;\n  }\n\n  .card-header [slot=\"header\"] {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .card-header h3 {\n    margin: 0;\n  }\n\n  .card-header sl-icon-button {\n    font-size: var(--sl-font-size-medium);\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCard className=\"card-header\">\n      <div slot=\"header\">\n        Header Title\n        <SlIconButton name=\"gear\"></SlIconButton>\n      </div>\n      This card has a header. You can put all sorts of things in it!\n    </SlCard>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Card with Footer\n\nFooters can be used to display actions, summaries, or other relevant content.\n\n```html:preview\n<sl-card class=\"card-footer\">\n  This card has a footer. You can put all sorts of things in it!\n\n  <div slot=\"footer\">\n    <sl-rating></sl-rating>\n    <sl-button variant=\"primary\">Preview</sl-button>\n  </div>\n</sl-card>\n\n<style>\n  .card-footer {\n    max-width: 300px;\n  }\n\n  .card-footer [slot='footer'] {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlCard from '@shoelace-style/shoelace/dist/react/card';\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst css = `\n  .card-footer {\n    max-width: 300px;\n  }\n\n  .card-footer [slot=\"footer\"] {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCard className=\"card-footer\">\n      This card has a footer. You can put all sorts of things in it!\n      <div slot=\"footer\">\n        <SlRating></SlRating>\n        <SlButton slot=\"footer\" variant=\"primary\">\n          Preview\n        </SlButton>\n      </div>\n    </SlCard>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Images\n\nCards accept an `image` slot. The image is displayed atop the card and stretches to fit.\n\n```html:preview\n<sl-card class=\"card-image\">\n  <img\n    slot=\"image\"\n    src=\"https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80\"\n    alt=\"A kitten walks towards camera on top of pallet.\"\n  />\n  This is a kitten, but not just any kitten. This kitten likes walking along pallets.\n</sl-card>\n\n<style>\n  .card-image {\n    max-width: 300px;\n  }\n</style>\n```\n\n```jsx:react\nimport SlCard from '@shoelace-style/shoelace/dist/react/card';\n\nconst css = `\n  .card-image {\n    max-width: 300px;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCard className=\"card-image\">\n      <img\n        slot=\"image\"\n        src=\"https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80\"\n        alt=\"A kitten walks towards camera on top of pallet.\"\n      />\n      This is a kitten, but not just any kitten. This kitten likes walking along pallets.\n    </SlCard>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/carousel-item.md",
    "content": "---\nmeta:\n  title: Carousel Item\n  description: A carousel item represent a slide within a carousel.\nlayout: component\n---\n\n```html:preview\n<sl-carousel pagination>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel pagination>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n:::tip\nAdditional demonstrations can be found in the [carousel examples](/components/carousel).\n:::\n"
  },
  {
    "path": "docs/pages/components/carousel.md",
    "content": "---\nmeta:\n  title: Carousel\n  description: Carousels display an arbitrary number of content slides along a horizontal or vertical axis.\nlayout: component\n---\n\n```html:preview\n<sl-carousel pagination navigation mouse-dragging loop>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <>\n    <SlCarousel pagination mouse-dragging>\n      <SlCarouselItem>\n        <img\n          alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n          src=\"/assets/examples/carousel/mountains.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n          src=\"/assets/examples/carousel/waterfall.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n          src=\"/assets/examples/carousel/sunset.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n          src=\"/assets/examples/carousel/field.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n          src=\"/assets/examples/carousel/valley.jpg\"\n        />\n      </SlCarouselItem>\n    </SlCarousel>\n  </>\n);\n```\n\n## Examples\n\n### Pagination\n\nUse the `pagination` attribute to show the total number of slides and the current slide as a set of interactive dots.\n\n```html:preview\n<sl-carousel pagination>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel pagination>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n### Navigation\n\nUse the `navigation` attribute to show previous and next buttons.\n\n```html:preview\n<sl-carousel navigation>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel navigation>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n### Looping\n\nBy default, the carousel will not advanced beyond the first and last slides. You can change this behavior and force the carousel to \"wrap\" with the `loop` attribute.\n\n```html:preview\n<sl-carousel loop navigation pagination>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel loop navigation pagination>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n### Autoplay\n\nThe carousel will automatically advance when the `autoplay` attribute is used. To change how long a slide is shown before advancing, set `autoplay-interval` to the desired number of milliseconds. For best results, use the `loop` attribute when autoplay is enabled. Note that autoplay will pause while the user interacts with the carousel.\n\n```html:preview\n<sl-carousel autoplay loop pagination>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel autoplay loop pagination>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </SlCarouselItem>\n    <SlCarouselItem>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n### Mouse Dragging\n\nThe carousel uses [scroll snap](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap) to position slides at various snap positions. This allows users to scroll through the slides very naturally, especially on touch devices. Unfortunately, desktop users won't be able to click and drag with a mouse, which can feel unnatural. Adding the `mouse-dragging` attribute can help with this.\n\nThis example is best demonstrated using a mouse. Try clicking and dragging the slide to move it. Then toggle the switch and try again.\n\n```html:preview\n<div class=\"mouse-dragging\">\n  <sl-carousel pagination>\n    <sl-carousel-item>\n      <img\n        alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n        src=\"/assets/examples/carousel/mountains.jpg\"\n      />\n    </sl-carousel-item>\n    <sl-carousel-item>\n      <img\n        alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n        src=\"/assets/examples/carousel/waterfall.jpg\"\n      />\n    </sl-carousel-item>\n    <sl-carousel-item>\n      <img\n        alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n        src=\"/assets/examples/carousel/sunset.jpg\"\n      />\n    </sl-carousel-item>\n    <sl-carousel-item>\n      <img\n        alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n        src=\"/assets/examples/carousel/field.jpg\"\n      />\n    </sl-carousel-item>\n    <sl-carousel-item>\n      <img\n        alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n        src=\"/assets/examples/carousel/valley.jpg\"\n      />\n    </sl-carousel-item>\n  </sl-carousel>\n\n  <sl-divider></sl-divider>\n\n  <sl-switch>Enable mouse dragging</sl-switch>\n</div>\n\n<script>\n  const container = document.querySelector('.mouse-dragging');\n  const carousel = container.querySelector('sl-carousel');\n  const toggle = container.querySelector('sl-switch');\n\n  toggle.addEventListener('sl-change', () => {\n    carousel.toggleAttribute('mouse-dragging', toggle.checked);\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => {\n  const [isEnabled, setIsEnabled] = useState(false);\n\n  return (\n    <>\n      <SlCarousel navigation mouseDragging={isEnabled}>\n        <SlCarouselItem>\n          <img\n            alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n            src=\"/assets/examples/carousel/mountains.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n            src=\"/assets/examples/carousel/waterfall.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n            src=\"/assets/examples/carousel/sunset.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n            src=\"/assets/examples/carousel/field.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n            src=\"/assets/examples/carousel/valley.jpg\"\n          />\n        </SlCarouselItem>\n      </SlCarousel>\n\n      <SlDivider></SlDivider>\n\n      <SlSwitch checked={isEnabled} onSlInput={() => setIsEnabled(!isEnabled)}>\n        Enable mouse dragging\n      </SlSwitch>\n    </>\n  );\n};\n```\n\n### Multiple Slides Per View\n\nThe `slides-per-page` attribute makes it possible to display multiple slides at a time. You can also use the `slides-per-move` attribute to advance more than once slide at a time, if desired.\n\n```html:preview\n<sl-carousel navigation pagination slides-per-page=\"2\" slides-per-move=\"2\">\n  <sl-carousel-item style=\"background: var(--sl-color-red-200);\">Slide 1</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-orange-200);\">Slide 2</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-yellow-200);\">Slide 3</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-green-200);\">Slide 4</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-blue-200);\">Slide 5</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-violet-200);\">Slide 6</sl-carousel-item>\n</sl-carousel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst App = () => (\n  <SlCarousel navigation pagination slidesPerPage={2} slidesPerMove={2}>\n    <SlCarouselItem style={{ background: 'var(--sl-color-red-200)' }}>Slide 1</SlCarouselItem>\n    <SlCarouselItem style={{ background: 'var(--sl-color-orange-200)' }}>Slide 2</SlCarouselItem>\n    <SlCarouselItem style={{ background: 'var(--sl-color-yellow-200)' }}>Slide 3</SlCarouselItem>\n    <SlCarouselItem style={{ background: 'var(--sl-color-green-200)' }}>Slide 4</SlCarouselItem>\n    <SlCarouselItem style={{ background: 'var(--sl-color-blue-200)' }}>Slide 5</SlCarouselItem>\n    <SlCarouselItem style={{ background: 'var(--sl-color-violet-200)' }}>Slide 6</SlCarouselItem>\n  </SlCarousel>\n);\n```\n\n{% endraw %}\n\n### Adding and Removing Slides\n\nThe content of the carousel can be changed by adding or removing carousel items. The carousel will update itself automatically.\n\n```html:preview\n<sl-carousel class=\"dynamic-carousel\" pagination navigation>\n  <sl-carousel-item style=\"background: var(--sl-color-red-200)\">Slide 1</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-orange-200)\">Slide 2</sl-carousel-item>\n  <sl-carousel-item style=\"background: var(--sl-color-yellow-200)\">Slide 3</sl-carousel-item>\n</sl-carousel>\n\n<div class=\"carousel-options\">\n  <sl-button id=\"dynamic-add\">Add slide</sl-button>\n  <sl-button id=\"dynamic-remove\">Remove slide</sl-button>\n</div>\n\n<style>\n  .dynamic-carousel {\n    --aspect-ratio: 3 / 2;\n  }\n\n  .dynamic-carousel ~ .carousel-options {\n    display: flex;\n    justify-content: center;\n    gap: var(--sl-spacing-x-small);\n    margin-top: var(--sl-spacing-large);\n  }\n\n  .dynamic-carousel sl-carousel-item {\n    flex: 0 0 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    font-size: var(--sl-font-size-2x-large);\n  }\n</style>\n\n<script>\n  (() => {\n    const dynamicCarousel = document.querySelector('.dynamic-carousel');\n    const dynamicAdd = document.querySelector('#dynamic-add');\n    const dynamicRemove = document.querySelector('#dynamic-remove');\n    const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'violet'];\n    let colorIndex = 2;\n\n    const addSlide = () => {\n      const slide = document.createElement('sl-carousel-item');\n      const color = colors[++colorIndex % colors.length];\n      slide.innerText = `Slide ${dynamicCarousel.children.length + 1}`;\n      slide.style.setProperty('background', `var(--sl-color-${color}-200)`);\n      dynamicCarousel.appendChild(slide);\n      dynamicRemove.disabled = false;\n    };\n\n    const removeSlide = () => {\n      const slide = dynamicCarousel.children[dynamicCarousel.children.length - 1];\n      const numSlides = dynamicCarousel.querySelectorAll('sl-carousel-item').length;\n\n      if (numSlides > 1) {\n        slide.remove();\n        colorIndex--;\n      }\n\n      dynamicRemove.disabled = numSlides - 1 <= 1;\n    };\n\n    dynamicAdd.addEventListener('click', addSlide);\n    dynamicRemove.addEventListener('click', removeSlide);\n  })();\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst css = `\n  .dynamic-carousel {\n    --aspect-ratio: 3 / 2;\n  }\n\n  .dynamic-carousel ~ .carousel-options {\n    display: flex;\n    justify-content: center;\n    margin-top: var(--sl-spacing-large);\n  }\n\n  .dynamic-carousel sl-carousel-item {\n    flex: 0 0 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    color: white;\n    font-size: var(--sl-font-size-2x-large);\n  }\n`;\n\nconst App = () => {\n  const [slides, setSlides] = useState(['#204ed8', '#be133d', '#6e28d9']);\n  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'violet'];\n\n  const addSlide = () => {\n    setSlides([...slides, getRandomColor()]);\n  };\n\n  const removeSlide = () => {\n    setSlides(slides.slice(0, -1));\n  };\n\n  return (\n    <>\n      <SlCarousel className=\"dynamic-carousel\" pagination navigation>\n        {slides.map((color, i) => (\n          <SlCarouselItem style={{ background: colors[i % colors.length }}>\n            Slide {i}\n          </SlCarouselItem>\n        ))}\n      </SlCarousel>\n\n      <div className=\"carousel-options\">\n        <SlButton onClick={addSlide}>Add slide</SlButton>\n        <SlButton onClick={removeSlide}>Remove slide</SlButton>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Vertical Scrolling\n\nSetting the `orientation` attribute to `vertical` will render the carousel in a vertical layout. If the content of your slides vary in height, you will need to set amn explicit `height` or `max-height` on the carousel using CSS.\n\n```html:preview\n<sl-carousel class=\"vertical\" pagination orientation=\"vertical\">\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n<style>\n  .vertical {\n    max-height: 400px;\n  }\n\n  .vertical::part(base) {\n    grid-template-areas: 'slides slides pagination';\n  }\n\n  .vertical::part(pagination) {\n    flex-direction: column;\n  }\n\n  .vertical::part(navigation) {\n    transform: rotate(90deg);\n    display: flex;\n  }\n</style>\n```\n\n```jsx:react\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\n\nconst css = `\n  .vertical {\n    max-height: 400px;\n  }\n\n  .vertical::part(base) {\n    grid-template-areas: 'slides slides pagination';\n  }\n\n  .vertical::part(pagination) {\n    flex-direction: column;\n  }\n\n  .vertical::part(navigation) {\n    transform: rotate(90deg);\n    display: flex;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCarousel className=\"vertical\" loop pagination orientation=\"vertical\">\n      <SlCarouselItem>\n        <img\n          alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n          src=\"/assets/examples/carousel/mountains.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n          src=\"/assets/examples/carousel/waterfall.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n          src=\"/assets/examples/carousel/sunset.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n          src=\"/assets/examples/carousel/field.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n          src=\"/assets/examples/carousel/valley.jpg\"\n        />\n      </SlCarouselItem>\n    </SlCarousel>\n    <style>{css}</style>\n  </>\n);\n```\n\n### Aspect Ratio\n\nUse the `--aspect-ratio` custom property to customize the size of the carousel's viewport from the default value of 16/9.\n\n```html:preview\n<sl-carousel class=\"aspect-ratio\" navigation pagination style=\"--aspect-ratio: 3/2;\">\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n\n<sl-divider></sl-divider>\n\n<sl-select label=\"Aspect ratio\" name=\"aspect\" value=\"3/2\">\n  <sl-option value=\"1/1\">1/1</sl-option>\n  <sl-option value=\"3/2\">3/2</sl-option>\n  <sl-option value=\"16/9\">16/9</sl-option>\n</sl-select>\n\n<script>\n  (() => {\n    const carousel = document.querySelector('sl-carousel.aspect-ratio');\n    const aspectRatio = document.querySelector('sl-select[name=\"aspect\"]');\n\n    aspectRatio.addEventListener('sl-change', () => {\n      carousel.style.setProperty('--aspect-ratio', aspectRatio.value);\n    });\n  })();\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\n\nconst App = () => {\n  const [aspectRatio, setAspectRatio] = useState('3/2');\n\n  return (\n    <>\n      <SlCarousel className=\"aspect-ratio\" navigation pagination style={{ '--aspect-ratio': aspectRatio }}>\n        <SlCarouselItem>\n          <img\n            alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n            src=\"/assets/examples/carousel/mountains.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n            src=\"/assets/examples/carousel/waterfall.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n            src=\"/assets/examples/carousel/sunset.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n            src=\"/assets/examples/carousel/field.jpg\"\n          />\n        </SlCarouselItem>\n        <SlCarouselItem>\n          <img\n            alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n            src=\"/assets/examples/carousel/valley.jpg\"\n          />\n        </SlCarouselItem>\n      </SlCarousel>\n\n      <SlDivider />\n\n      <SlSelect\n        label=\"Aspect ratio\"\n        name=\"aspect\"\n        value={aspectRatio}\n        onSlChange={event => setAspectRatio(event.target.value)}\n      >\n        <SlOption value=\"1 / 1\">1 / 1</SlOption>\n        <SlOption value=\"3 / 2\">3 / 2</SlOption>\n        <SlOption value=\"16 / 9\">16 / 9</SlOption>\n      </SlSelect>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Scroll Hint\n\nUse the `--scroll-hint` custom property to add inline padding in horizontal carousels and block padding in vertical carousels. This will make the closest slides slightly visible, hinting that there are more items in the carousel.\n\n```html:preview\n<sl-carousel class=\"scroll-hint\" pagination style=\"--scroll-hint: 10%;\">\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => (\n  <>\n    <SlCarousel className=\"scroll-hint\" pagination style={{ '--scroll-hint': '10%' }}>\n      <SlCarouselItem>\n        <img\n          alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n          src=\"/assets/examples/carousel/mountains.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n          src=\"/assets/examples/carousel/waterfall.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n          src=\"/assets/examples/carousel/sunset.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n          src=\"/assets/examples/carousel/field.jpg\"\n        />\n      </SlCarouselItem>\n      <SlCarouselItem>\n        <img\n          alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n          src=\"/assets/examples/carousel/valley.jpg\"\n        />\n      </SlCarouselItem>\n    </SlCarousel>\n  </>\n);\n```\n\n{% endraw %}\n\n### Gallery Example\n\nThe carousel has a robust API that makes it possible to extend and customize. This example syncs the active slide with a set of thumbnails, effectively creating a gallery-style carousel.\n\n```html:preview\n<sl-carousel class=\"carousel-thumbnails\" navigation loop>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun shines on the mountains and trees (by Adam Kool on Unsplash)\"\n      src=\"/assets/examples/carousel/mountains.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A waterfall in the middle of a forest (by Thomas Kelly on Unsplash)\"\n      src=\"/assets/examples/carousel/waterfall.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"The sun is setting over a lavender field (by Leonard Cotte on Unsplash)\"\n      src=\"/assets/examples/carousel/sunset.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A field of grass with the sun setting in the background (by Sapan Patel on Unsplash)\"\n      src=\"/assets/examples/carousel/field.jpg\"\n    />\n  </sl-carousel-item>\n  <sl-carousel-item>\n    <img\n      alt=\"A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash)\"\n      src=\"/assets/examples/carousel/valley.jpg\"\n    />\n  </sl-carousel-item>\n</sl-carousel>\n\n<div class=\"thumbnails\">\n  <div class=\"thumbnails__scroller\">\n    <img alt=\"Thumbnail by 1\" class=\"thumbnails__image active\" src=\"/assets/examples/carousel/mountains.jpg\" />\n    <img alt=\"Thumbnail by 2\" class=\"thumbnails__image\" src=\"/assets/examples/carousel/waterfall.jpg\" />\n    <img alt=\"Thumbnail by 3\" class=\"thumbnails__image\" src=\"/assets/examples/carousel/sunset.jpg\" />\n    <img alt=\"Thumbnail by 4\" class=\"thumbnails__image\" src=\"/assets/examples/carousel/field.jpg\" />\n    <img alt=\"Thumbnail by 5\" class=\"thumbnails__image\" src=\"/assets/examples/carousel/valley.jpg\" />\n  </div>\n</div>\n\n<style>\n  .carousel-thumbnails {\n    --slide-aspect-ratio: 3 / 2;\n  }\n\n  .thumbnails {\n    display: flex;\n    justify-content: center;\n  }\n\n  .thumbnails__scroller {\n    display: flex;\n    gap: var(--sl-spacing-small);\n    overflow-x: auto;\n    scrollbar-width: none;\n    scroll-behavior: smooth;\n    scroll-padding: var(--sl-spacing-small);\n  }\n\n  .thumbnails__scroller::-webkit-scrollbar {\n    display: none;\n  }\n\n  .thumbnails__image {\n    width: 64px;\n    height: 64px;\n    object-fit: cover;\n\n    opacity: 0.3;\n    will-change: opacity;\n    transition: 250ms opacity;\n\n    cursor: pointer;\n  }\n\n  .thumbnails__image.active {\n    opacity: 1;\n  }\n</style>\n\n<script>\n  {\n    const carousel = document.querySelector('.carousel-thumbnails');\n    const scroller = document.querySelector('.thumbnails__scroller');\n    const thumbnails = document.querySelectorAll('.thumbnails__image');\n\n    scroller.addEventListener('click', e => {\n      const target = e.target;\n\n      if (target.matches('.thumbnails__image')) {\n        const index = [...thumbnails].indexOf(target);\n        carousel.goToSlide(index);\n      }\n    });\n\n    carousel.addEventListener('sl-slide-change', e => {\n      const slideIndex = e.detail.index;\n\n      [...thumbnails].forEach((thumb, i) => {\n        thumb.classList.toggle('active', i === slideIndex);\n        if (i === slideIndex) {\n          thumb.scrollIntoView({\n            block: 'nearest'\n          });\n        }\n      });\n    });\n  }\n</script>\n```\n\n```jsx:react\nimport { useRef } from 'react';\nimport SlCarousel from '@shoelace-style/shoelace/dist/react/carousel';\nimport SlCarouselItem from '@shoelace-style/shoelace/dist/react/carousel-item';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst css = `\n  .carousel-thumbnails {\n    --slide-aspect-ratio: 3 / 2;\n  }\n\n  .thumbnails {\n    display: flex;\n    justify-content: center;\n  }\n\n  .thumbnails__scroller {\n    display: flex;\n    gap: var(--sl-spacing-small);\n    overflow-x: auto;\n    scrollbar-width: none;\n    scroll-behavior: smooth;\n    scroll-padding: var(--sl-spacing-small);\n  }\n\n  .thumbnails__scroller::-webkit-scrollbar {\n    display: none;\n  }\n\n  .thumbnails__image {\n    width: 64px;\n    height: 64px;\n    object-fit: cover;\n\n    opacity: 0.3;\n    will-change: opacity;\n    transition: 250ms opacity;\n\n    cursor: pointer;\n  }\n\n  .thumbnails__image.active {\n    opacity: 1;\n  }\n`;\n\nconst images = [\n  {\n    src: '/assets/examples/carousel/mountains.jpg',\n    alt: 'The sun shines on the mountains and trees (by Adam Kool on Unsplash'\n  },\n  {\n    src: '/assets/examples/carousel/waterfall.jpg',\n    alt: 'A waterfall in the middle of a forest (by Thomas Kelly on Unsplash'\n  },\n  {\n    src: '/assets/examples/carousel/sunset.jpg',\n    alt: 'The sun is setting over a lavender field (by Leonard Cotte on Unsplash'\n  },\n  {\n    src: '/assets/examples/carousel/field.jpg',\n    alt: 'A field of grass with the sun setting in the background (by Sapan Patel on Unsplash'\n  },\n  {\n    src: '/assets/examples/carousel/valley.jpg',\n    alt: 'A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash'\n  }\n];\n\nconst App = () => {\n  const carouselRef = useRef();\n  const thumbnailsRef = useRef();\n  const [currentSlide, setCurrentSlide] = useState(0);\n\n  useEffect(() => {\n    const thumbnails = Array.from(thumbnailsRef.current.querySelectorAll('.thumbnails__image'));\n\n    thumbnails[currentSlide]..scrollIntoView({\n      block: 'nearest'\n    });\n  }, [currentSlide]);\n\n  const handleThumbnailClick = (index) => {\n    carouselRef.current.goToSlide(index);\n  }\n\n  const handleSlideChange = (event) => {\n    const slideIndex = e.detail.index;\n    setCurrentSlide(slideIndex);\n  }\n\n  return (\n    <>\n      <SlCarousel className=\"carousel-thumbnails\" navigation loop onSlSlideChange={handleSlideChange}>\n        {images.map({ src, alt }) => (\n          <SlCarouselItem>\n            <img\n              alt={alt}\n              src={src}\n            />\n          </SlCarouselItem>\n        )}\n      </SlCarousel>\n\n      <div class=\"thumbnails\">\n        <div class=\"thumbnails__scroller\">\n          {images.map({ src, alt }, i) => (\n            <img\n              alt={`Thumbnail by ${i + 1}`}\n              className={`thumbnails__image ${i === currentSlide ? 'active' : ''}`}\n              onClick={() => handleThumbnailClick(i)}\n              src={src}\n            />\n          )}\n        </div>\n      </div>\n      <style>{css}</style>\n    </>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/checkbox.md",
    "content": "---\nmeta:\n  title: Checkbox\n  description: Checkboxes allow the user to toggle an option on or off.\nlayout: component\n---\n\n```html:preview\n<sl-checkbox>Checkbox</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlCheckbox>Checkbox</SlCheckbox>;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Checked\n\nUse the `checked` attribute to activate the checkbox.\n\n```html:preview\n<sl-checkbox checked>Checked</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlCheckbox checked>Checked</SlCheckbox>;\n```\n\n### Indeterminate\n\nUse the `indeterminate` attribute to make the checkbox indeterminate.\n\n```html:preview\n<sl-checkbox indeterminate>Indeterminate</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlCheckbox indeterminate>Indeterminate</SlCheckbox>;\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable the checkbox.\n\n```html:preview\n<sl-checkbox disabled>Disabled</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlCheckbox disabled>Disabled</SlCheckbox>;\n```\n\n### Sizes\n\nUse the `size` attribute to change a checkbox's size.\n\n```html:preview\n<sl-checkbox size=\"small\">Small</sl-checkbox>\n<br />\n<sl-checkbox size=\"medium\">Medium</sl-checkbox>\n<br />\n<sl-checkbox size=\"large\">Large</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => (\n  <>\n    <SlCheckbox size=\"small\">Small</SlCheckbox>\n    <br />\n    <SlCheckbox size=\"medium\">Medium</SlCheckbox>\n    <br />\n    <SlCheckbox size=\"large\">Large</SlCheckbox>\n  </>\n);\n```\n\n### Help Text\n\nAdd descriptive help text to a switch with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-checkbox help-text=\"What should the user know about the checkbox?\">Label</sl-checkbox>\n```\n\n```jsx:react\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlCheckbox help-text=\"What should the user know about the switch?\">Label</SlCheckbox>;\n```\n\n### Custom Validity\n\nUse the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.\n\n```html:preview\n<form class=\"custom-validity\">\n  <sl-checkbox>Check me</sl-checkbox>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\" style=\"margin-top: 1rem;\">Submit</sl-button>\n</form>\n<script>\n  const form = document.querySelector('.custom-validity');\n  const checkbox = form.querySelector('sl-checkbox');\n  const errorMessage = `Don't forget to check me!`;\n\n  // Set initial validity as soon as the element is defined\n  customElements.whenDefined('sl-checkbox').then(async () => {\n    await checkbox.updateComplete;\n    checkbox.setCustomValidity(errorMessage);\n  });\n\n  // Update validity on change\n  checkbox.addEventListener('sl-change', () => {\n    checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);\n  });\n\n  // Handle submit\n  form.addEventListener('submit', event => {\n    event.preventDefault();\n    alert('All fields are valid!');\n  });\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useEffect, useRef } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => {\n  const checkbox = useRef(null);\n  const errorMessage = `Don't forget to check me!`;\n\n  function handleChange() {\n    checkbox.current.setCustomValidity(checkbox.current.checked ? '' : errorMessage);\n  }\n\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  useEffect(() => {\n    checkbox.current.setCustomValidity(errorMessage);\n  }, []);\n\n  return (\n    <form class=\"custom-validity\" onSubmit={handleSubmit}>\n      <SlCheckbox ref={checkbox} onSlChange={handleChange}>\n        Check me\n      </SlCheckbox>\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\" style={{ marginTop: '1rem' }}>\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/color-picker.md",
    "content": "---\nmeta:\n  title: Color Picker\n  description: Color pickers allow the user to select a color.\nlayout: component\n---\n\n```html:preview\n<sl-color-picker label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => <SlColorPicker label=\"Select a color\" />;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Initial Value\n\nUse the `value` attribute to set an initial value for the color picker.\n\n```html:preview\n<sl-color-picker value=\"#4a90e2\" label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => <SlColorPicker value=\"#4a90e2\" label=\"Select a color\" />;\n```\n\n### Opacity\n\nUse the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, HSLA, or HSVA based on `format`.\n\n```html:preview\n<sl-color-picker value=\"#f5a623ff\" opacity label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => <SlColorPicker opacity label=\"Select a color\" />;\n```\n\n### Formats\n\nSet the color picker's format with the `format` attribute. Valid options include `hex`, `rgb`, `hsl`, and `hsv`. Note that the color picker's input will accept any parsable format (including CSS color names) regardless of this option.\n\nTo prevent users from toggling the format themselves, add the `no-format-toggle` attribute.\n\n```html:preview\n<sl-color-picker format=\"hex\" value=\"#4a90e2\" label=\"Select a color\"></sl-color-picker>\n<sl-color-picker format=\"rgb\" value=\"rgb(80, 227, 194)\" label=\"Select a color\"></sl-color-picker>\n<sl-color-picker format=\"hsl\" value=\"hsl(290, 87%, 47%)\" label=\"Select a color\"></sl-color-picker>\n<sl-color-picker format=\"hsv\" value=\"hsv(55, 89%, 97%)\" label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => (\n  <>\n    <SlColorPicker format=\"hex\" value=\"#4a90e2\" />\n    <SlColorPicker format=\"rgb\" value=\"rgb(80, 227, 194)\" />\n    <SlColorPicker format=\"hsl\" value=\"hsl(290, 87%, 47%)\" />\n    <SlColorPicker format=\"hsv\" value=\"hsv(55, 89%, 97%)\" />\n  </>\n);\n```\n\n### Swatches\n\nUse the `swatches` attribute to add convenient presets to the color picker. Any format the color picker can parse is acceptable (including CSS color names), but each value must be separated by a semicolon (`;`). Alternatively, you can pass an array of color values to this property using JavaScript.\n\n```html:preview\n<sl-color-picker\n  label=\"Select a color\"\n  swatches=\"\n    #d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;\n    #4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;\n  \"\n></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => (\n  <SlColorPicker\n    label=\"Select a color\"\n    swatches=\"\n      #d0021b; #f5a623; #f8e71c; #8b572a; #7ed321; #417505; #bd10e0; #9013fe;\n      #4a90e2; #50e3c2; #b8e986; #000; #444; #888; #ccc; #fff;\n    \"\n  />\n);\n```\n\n### Sizes\n\nUse the `size` attribute to change the color picker's trigger size.\n\n```html:preview\n<sl-color-picker size=\"small\" label=\"Select a color\"></sl-color-picker>\n<sl-color-picker size=\"medium\" label=\"Select a color\"></sl-color-picker>\n<sl-color-picker size=\"large\" label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => (\n  <>\n    <SlColorPicker size=\"small\" label=\"Select a color\" />\n    <SlColorPicker size=\"medium\" label=\"Select a color\" />\n    <SlColorPicker size=\"large\" label=\"Select a color\" />\n  </>\n);\n```\n\n### Inline\n\nThe color picker can be rendered inline instead of in a dropdown using the `inline` attribute.\n\n```html:preview\n<sl-color-picker inline label=\"Select a color\"></sl-color-picker>\n```\n\n```jsx:react\nimport SlColorPicker from '@shoelace-style/shoelace/dist/react/color-picker';\n\nconst App = () => <SlColorPicker inline label=\"Select a color\" />;\n```\n"
  },
  {
    "path": "docs/pages/components/copy-button.md",
    "content": "---\nmeta:\n  title: Copy Button\n  description: Copies data to the clipboard when the user clicks the button.\nlayout: component\n---\n\n```html:preview\n<sl-copy-button value=\"Shoelace rocks!\"></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst App = () => (\n  <SlCopyButton value=\"Shoelace rocks!\" />\n);\n```\n\n## Examples\n\n### Custom Labels\n\nCopy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes.\n\n```html:preview\n<sl-copy-button\n  value=\"Custom labels are easy\"\n  copy-label=\"Click to copy\"\n  success-label=\"You did it!\"\n  error-label=\"Whoops, your browser doesn't support this!\"\n></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst App = () => (\n  <SlCopyButton\n    value=\"Custom labels are easy\"\n    copy-label=\"Click to copy\"\n    success-label=\"You did it!\"\n    error-label=\"Whoops, your browser doesn't support this!\"\n  />\n);\n```\n\n### Custom Icons\n\nUse the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [`<sl-icon>`](/components/icon) or your own images.\n\n```html:preview\n<sl-copy-button value=\"Copied from a custom button\">\n  <sl-icon slot=\"copy-icon\" name=\"clipboard\"></sl-icon>\n  <sl-icon slot=\"success-icon\" name=\"clipboard-check\"></sl-icon>\n  <sl-icon slot=\"error-icon\" name=\"clipboard-x\"></sl-icon>\n</sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\nimport { SlIcon } from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <SlCopyButton value=\"Copied from a custom button\">\n      <SlIcon slot=\"copy-icon\" name=\"clipboard\" />\n      <SlIcon slot=\"success-icon\" name=\"clipboard-check\" />\n      <SlIcon slot=\"error-icon\" name=\"clipboard-x\" />\n    </SlCopyButton>\n  </>\n);\n```\n\n### Copying Values From Other Elements\n\nNormally, the data that gets copied will come from the component's `value` attribute, but you can copy data from any element within the same document by providing its `id` to the `from` attribute.\n\nWhen using the `from` attribute, the element's [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) will be copied by default. Passing an attribute or property modifier will let you copy data from one of the element's attributes or properties instead.\n\nTo copy data from an attribute, use `from=\"id[attr]\"` where `id` is the id of the target element and `attr` is the name of the attribute you'd like to copy. To copy data from a property, use `from=\"id.prop\"` where `id` is the id of the target element and `prop` is the name of the property you'd like to copy.\n\n```html:preview\n<!-- Copies the span's textContent -->\n<span id=\"my-phone\">+1 (234) 456-7890</span>\n<sl-copy-button from=\"my-phone\"></sl-copy-button>\n\n<br><br>\n\n<!-- Copies the input's \"value\" property -->\n<sl-input id=\"my-input\" type=\"text\" value=\"User input\" style=\"display: inline-block; max-width: 300px;\"></sl-input>\n<sl-copy-button from=\"my-input.value\"></sl-copy-button>\n\n<br><br>\n\n<!-- Copies the link's \"href\" attribute -->\n<a id=\"my-link\" href=\"https://shoelace.style/\">Shoelace Website</a>\n<sl-copy-button from=\"my-link[href]\"></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\nimport { SlInput } from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => (\n  <>\n    {/* Copies the span's textContent */}\n    <span id=\"my-phone\">+1 (234) 456-7890</span>\n    <SlCopyButton from=\"my-phone\" />\n\n    <br /><br />\n\n    {/* Copies the input's \"value\" property */}\n    <SlInput id=\"my-input\" type=\"text\" />\n    <SlCopyButton from=\"my-input.value\" />\n\n    <br /><br />\n\n    {/* Copies the link's \"href\" attribute */}\n    <a id=\"my-link\" href=\"https://shoelace.style/\">Shoelace Website</a>\n    <SlCopyButton from=\"my-link[href]\" />\n  </>\n);\n```\n\n### Handling Errors\n\nA copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `sl-error` event will be emitted.\n\nThis example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively.\n\n```html:preview\n<sl-copy-button from=\"i-do-not-exist\"></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst App = () => (\n  <SlCopyButton from=\"i-do-not-exist\" />\n);\n```\n\n### Disabled\n\nCopy buttons can be disabled by adding the `disabled` attribute.\n\n```html:preview\n<sl-copy-button value=\"You can't copy me\" disabled></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst App = () => (\n  <SlCopyButton value=\"You can't copy me\" disabled />\n);\n```\n\n### Changing Feedback Duration\n\nA success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.\n\n```html:preview\n<sl-copy-button value=\"Shoelace rocks!\" feedback-duration=\"250\"></sl-copy-button>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst App = () => (\n  <SlCopyButton value=\"Shoelace rocks!\" feedback-duration={250} />\n);\n```\n\n### Custom Styles\n\nYou can customize the button to your liking with CSS.\n\n```html:preview\n<sl-copy-button value=\"I'm so stylish\" class=\"custom-styles\">\n  <sl-icon slot=\"copy-icon\" name=\"asterisk\"></sl-icon>\n  <sl-icon slot=\"success-icon\" name=\"check-lg\"></sl-icon>\n  <sl-icon slot=\"error-icon\" name=\"x-lg\"></sl-icon>\n</sl-copy-button>\n\n<style>\n  .custom-styles {\n    --success-color: white;\n    --error-color: white;\n    color: white;\n  }\n\n  .custom-styles::part(button) {\n    background-color: #ff1493;\n    border: solid 4px #ff7ac1;\n    border-right-color: #ad005c;\n    border-bottom-color: #ad005c;\n    border-radius: 0;\n    transition: 100ms scale ease-in-out, 100ms translate ease-in-out;\n  }\n\n  .custom-styles::part(button):hover {\n    scale: 1.1;\n  }\n\n  .custom-styles::part(button):active {\n    translate: 0 2px;\n  }\n\n  .custom-styles::part(button):focus-visible {\n    outline: dashed 2px deeppink;\n    outline-offset: 4px;\n  }\n</style>\n```\n\n```jsx:react\nimport { SlCopyButton } from '@shoelace-style/shoelace/dist/react/copy-button';\n\nconst css = `\n  .custom-styles {\n    --success-color: white;\n    --error-color: white;\n    color: white;\n  }\n\n  .custom-styles::part(button) {\n    background-color: #ff1493;\n    border: solid 4px #ff7ac1;\n    border-right-color: #ad005c;\n    border-bottom-color: #ad005c;\n    border-radius: 0;\n    transition: 100ms scale ease-in-out, 100ms translate ease-in-out;\n  }\n\n  .custom-styles::part(button):hover {\n    scale: 1.1;\n  }\n\n  .custom-styles::part(button):active {\n    translate: 0 2px;\n  }\n\n  .custom-styles::part(button):focus-visible {\n    outline: dashed 2px deeppink;\n    outline-offset: 4px;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlCopyButton value=\"I'm so stylish\" className=\"custom-styles\" />\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/details.md",
    "content": "---\nmeta:\n  title: Details\n  description: Details show a brief summary and expand to show additional content.\nlayout: component\n---\n\n<!-- cspell:dictionaries lorem-ipsum -->\n\n```html:preview\n<sl-details summary=\"Toggle Me\">\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n</sl-details>\n```\n\n```jsx:react\nimport SlDetails from '@shoelace-style/shoelace/dist/react/details';\n\nconst App = () => (\n  <SlDetails summary=\"Toggle Me\">\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n    aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n  </SlDetails>\n);\n```\n\n## Examples\n\n### Disabled\n\nUse the `disable` attribute to prevent the details from expanding.\n\n```html:preview\n<sl-details summary=\"Disabled\" disabled>\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n</sl-details>\n```\n\n```jsx:react\nimport SlDetails from '@shoelace-style/shoelace/dist/react/details';\n\nconst App = () => (\n  <SlDetails summary=\"Disabled\" disabled>\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n    aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n  </SlDetails>\n);\n```\n\n### Customizing the Summary Icon\n\nUse the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively. To disable the animation, override the `rotate` property on the `summary-icon` part as shown below.\n\n```html:preview\n<sl-details summary=\"Toggle Me\" class=\"custom-icons\">\n  <sl-icon name=\"plus-square\" slot=\"expand-icon\"></sl-icon>\n  <sl-icon name=\"dash-square\" slot=\"collapse-icon\"></sl-icon>\n\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n</sl-details>\n\n<style>\n  sl-details.custom-icons::part(summary-icon) {\n    /* Disable the expand/collapse animation */\n    rotate: none;\n  }\n</style>\n```\n\n```jsx:react\nimport SlDetails from '@shoelace-style/shoelace/dist/react/details';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst css = `\n  sl-details.custom-icon::part(summary-icon) {\n    /* Disable the expand/collapse animation */\n    rotate: none;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlDetails summary=\"Toggle Me\" class=\"custom-icon\">\n      <SlIcon name=\"plus-square\" slot=\"expand-icon\" />\n      <SlIcon name=\"dash-square\" slot=\"collapse-icon\" />\n      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n      magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n      consequat.\n    </SlDetails>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Grouping Details\n\nDetails are designed to function independently, but you can simulate a group or \"accordion\" where only one is shown at a time by listening for the `sl-show` event.\n\n```html:preview\n<div class=\"details-group-example\">\n  <sl-details summary=\"First\" open>\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n    aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n  </sl-details>\n\n  <sl-details summary=\"Second\">\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n    aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n  </sl-details>\n\n  <sl-details summary=\"Third\">\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna\n    aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n  </sl-details>\n</div>\n\n<script>\n  const container = document.querySelector('.details-group-example');\n\n  // Close all other details when one is shown\n  container.addEventListener('sl-show', event => {\n    if (event.target.localName === 'sl-details') {\n      [...container.querySelectorAll('sl-details')].map(details => (details.open = event.target === details));\n    }\n  });\n</script>\n\n<style>\n  .details-group-example sl-details:not(:last-of-type) {\n    margin-bottom: var(--sl-spacing-2x-small);\n  }\n</style>\n```\n"
  },
  {
    "path": "docs/pages/components/dialog.md",
    "content": "---\nmeta:\n  title: Dialog\n  description: 'Dialogs, sometimes called \"modals\", appear above the page and require the user''s immediate attention.'\nlayout: component\n---\n\n<!-- cspell:dictionaries lorem-ipsum -->\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-overview\">\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-overview');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} onSlAfterHide={() => setOpen(false)}>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n## Examples\n\n### Custom Width\n\nUse the `--width` custom property to set the dialog's width.\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-width\" style=\"--width: 50vw;\">\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-width');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} style={{ '--width': '50vw' }} onSlAfterHide={() => setOpen(false)}>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Scrolling\n\nBy design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-scrolling\">\n  <div style=\"height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;\">\n    <p>Scroll down and give it a try! 👇</p>\n  </div>\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-scrolling');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <div\n          style={{\n            height: '150vh',\n            border: 'dashed 2px var(--sl-color-neutral-200)',\n            padding: '0 1rem'\n          }}\n        >\n          <p>Scroll down and give it a try! 👇</p>\n        </div>\n\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Header Actions\n\nThe header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-header-actions\">\n  <sl-icon-button class=\"new-window\" slot=\"header-actions\" name=\"box-arrow-up-right\"></sl-icon-button>\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-header-actions');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n  const newWindowButton = dialog.querySelector('.new-window');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n  newWindowButton.addEventListener('click', () => window.open(location.href));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <SlIconButton\n          class=\"new-window\"\n          slot=\"header-actions\"\n          name=\"box-arrow-up-right\"\n          onClick={() => window.open(location.href)}\n        />\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n### Preventing the Dialog from Closing\n\nBy default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.\n\nTo keep the dialog open in such cases, you can cancel the `sl-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it.\n\nYou can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-deny-close\">\n  This dialog will not close when you click on the overlay.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-deny-close');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n\n  // Prevent the dialog from closing when the user clicks on the overlay\n  dialog.addEventListener('sl-request-close', event => {\n    if (event.detail.source === 'overlay') {\n      event.preventDefault();\n    }\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  // Prevent the dialog from closing when the user clicks on the overlay\n  function handleRequestClose(event) {\n    if (event.detail.source === 'overlay') {\n      event.preventDefault();\n    }\n  }\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} onSlRequestClose={handleRequestClose} onSlAfterHide={() => setOpen(false)}>\n        This dialog will not close when you click on the overlay.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n### Customizing Initial Focus\n\nBy default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.\n\n```html:preview\n<sl-dialog label=\"Dialog\" class=\"dialog-focus\">\n  <sl-input autofocus placeholder=\"I will have focus when the dialog is opened\"></sl-input>\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-dialog>\n\n<sl-button>Open Dialog</sl-button>\n\n<script>\n  const dialog = document.querySelector('.dialog-focus');\n  const input = dialog.querySelector('sl-input');\n  const openButton = dialog.nextElementSibling;\n  const closeButton = dialog.querySelector('sl-button[slot=\"footer\"]');\n\n  openButton.addEventListener('click', () => dialog.show());\n  closeButton.addEventListener('click', () => dialog.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDialog from '@shoelace-style/shoelace/dist/react/dialog';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDialog label=\"Dialog\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <SlInput autofocus placeholder=\"I will have focus when the dialog is opened\" />\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDialog>\n\n      <SlButton onClick={() => setOpen(true)}>Open Dialog</SlButton>\n    </>\n  );\n};\n```\n\n:::tip\nYou can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.\n:::\n"
  },
  {
    "path": "docs/pages/components/divider.md",
    "content": "---\nmeta:\n  title: Divider\n  description: Dividers are used to visually separate or group elements.\nlayout: component\n---\n\n```html:preview\n<sl-divider></sl-divider>\n```\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\n\nconst App = () => <SlDivider />;\n```\n\n## Examples\n\n### Width\n\nUse the `--width` custom property to change the width of the divider.\n\n```html:preview\n<sl-divider style=\"--width: 4px;\"></sl-divider>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\n\nconst App = () => <SlDivider style={{ '--width': '4px' }} />;\n```\n\n{% endraw %}\n\n### Color\n\nUse the `--color` custom property to change the color of the divider.\n\n```html:preview\n<sl-divider style=\"--color: tomato;\"></sl-divider>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\n\nconst App = () => <SlDivider style={{ '--color': 'tomato' }} />;\n```\n\n{% endraw %}\n\n### Spacing\n\nUse the `--spacing` custom property to change the amount of space between the divider and it's neighboring elements.\n\n```html:preview\n<div style=\"text-align: center;\">\n  Above\n  <sl-divider style=\"--spacing: 2rem;\"></sl-divider>\n  Below\n</div>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\n\nconst App = () => (\n  <>\n    Above\n    <SlDivider style={{ '--spacing': '2rem' }} />\n    Below\n  </>\n);\n```\n\n{% endraw %}\n\n### Vertical\n\nAdd the `vertical` attribute to draw the divider in a vertical orientation. The divider will span the full height of its container. Vertical dividers work especially well inside of a flex container.\n\n```html:preview\n<div style=\"display: flex; align-items: center; height: 2rem;\">\n  First\n  <sl-divider vertical></sl-divider>\n  Middle\n  <sl-divider vertical></sl-divider>\n  Last\n</div>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\n\nconst App = () => (\n  <div\n    style={{\n      display: 'flex',\n      alignItems: 'center',\n      height: '2rem'\n    }}\n  >\n    First\n    <SlDivider vertical />\n    Middle\n    <SlDivider vertical />\n    Last\n  </div>\n);\n```\n\n{% endraw %}\n\n### Menu Dividers\n\nUse dividers in [menus](/components/menu) to visually group menu items.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item value=\"1\">Option 1</sl-menu-item>\n  <sl-menu-item value=\"2\">Option 2</sl-menu-item>\n  <sl-menu-item value=\"3\">Option 3</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item value=\"4\">Option 4</sl-menu-item>\n  <sl-menu-item value=\"5\">Option 5</sl-menu-item>\n  <sl-menu-item value=\"6\">Option 6</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem value=\"1\">Option 1</SlMenuItem>\n    <SlMenuItem value=\"2\">Option 2</SlMenuItem>\n    <SlMenuItem value=\"3\">Option 3</SlMenuItem>\n    <sl-divider />\n    <SlMenuItem value=\"4\">Option 4</SlMenuItem>\n    <SlMenuItem value=\"5\">Option 5</SlMenuItem>\n    <SlMenuItem value=\"6\">Option 6</SlMenuItem>\n  </SlMenu>\n);\n```\n\n### Tailwind users\n\nUsing TailwindCSS with Shoelace [may override divider styles](https://github.com/shoelace-style/shoelace/issues/2088), making them invisible. As a workaround, add this to your Tailwind config file.\n\n```css\n@layer base {\n  sl-divider:not([vertical]) {\n    border-top: solid var(--width) var(--color);\n  }\n\n  sl-divider[vertical] {\n    border-left: solid var(--width) var(--color);\n  }\n}\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/drawer.md",
    "content": "---\nmeta:\n  title: Drawer\n  description: Drawers slide in from a container to expose additional options and information.\nlayout: component\n---\n\n<!-- cspell:dictionaries lorem-ipsum -->\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-overview\">\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-overview');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlAfterHide={() => setOpen(false)}>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n## Examples\n\n### Slide in From Start\n\nBy default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.\n\n```html:preview\n<sl-drawer label=\"Drawer\" placement=\"start\" class=\"drawer-placement-start\">\n  This drawer slides in from the start.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-placement-start');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" placement=\"start\" open={open} onSlAfterHide={() => setOpen(false)}>\n        This drawer slides in from the start.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n### Slide in From Top\n\nTo make the drawer slide in from the top, set the `placement` attribute to `top`.\n\n```html:preview\n<sl-drawer label=\"Drawer\" placement=\"top\" class=\"drawer-placement-top\">\n  This drawer slides in from the top.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-placement-top');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" placement=\"top\" open={open} onSlAfterHide={() => setOpen(false)}>\n        This drawer slides in from the top.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n### Slide in From Bottom\n\nTo make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.\n\n```html:preview\n<sl-drawer label=\"Drawer\" placement=\"bottom\" class=\"drawer-placement-bottom\">\n  This drawer slides in from the bottom.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-placement-bottom');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" placement=\"bottom\" open={open} onSlAfterHide={() => setOpen(false)}>\n        This drawer slides in from the bottom.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n### Contained to an Element\n\nBy default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.\n\nUnlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with [[Escape]]. This is intentional to allow users to interact with elements outside of the drawer.\n\n```html:preview\n<div\n  style=\"position: relative; border: solid 2px var(--sl-panel-border-color); height: 300px; padding: 1rem; margin-bottom: 1rem;\"\n>\n  The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens.\n\n  <sl-drawer label=\"Drawer\" contained class=\"drawer-contained\" style=\"--size: 50%;\">\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n    <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n  </sl-drawer>\n</div>\n\n<sl-button>Toggle Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-contained');\n  const openButton = drawer.parentElement.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => (drawer.open = !drawer.open));\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <div\n        style={{\n          position: 'relative',\n          border: 'solid 2px var(--sl-panel-border-color)',\n          height: '300px',\n          padding: '1rem',\n          marginBottom: '1rem'\n        }}\n      >\n        The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer\n        opens.\n        <SlDrawer\n          label=\"Drawer\"\n          contained\n          no-modal\n          open={open}\n          onSlAfterHide={() => setOpen(false)}\n          style={{ '--size': '50%' }}\n        >\n          Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n          <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n            Close\n          </SlButton>\n        </SlDrawer>\n      </div>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Custom Size\n\nUse the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-custom-size\" style=\"--size: 50vw;\">\n  This drawer is always 50% of the viewport.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-custom-size');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>\n        This drawer is always 50% of the viewport.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Scrolling\n\nBy design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-scrolling\">\n  <div style=\"height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;\">\n    <p>Scroll down and give it a try! 👇</p>\n  </div>\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-scrolling');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <div\n          style={{\n            height: '150vh',\n            border: 'dashed 2px var(--sl-color-neutral-200)',\n            padding: '0 1rem'\n          }}\n        >\n          <p>Scroll down and give it a try! 👇</p>\n        </div>\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Header Actions\n\nThe header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-header-actions\">\n  <sl-icon-button class=\"new-window\" slot=\"header-actions\" name=\"box-arrow-up-right\"></sl-icon-button>\n  Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-header-actions');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n  const newWindowButton = drawer.querySelector('.new-window');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n  newWindowButton.addEventListener('click', () => window.open(location.href));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <SlIconButton slot=\"header-actions\" name=\"box-arrow-up-right\" onClick={() => window.open(location.href)} />\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n### Preventing the Drawer from Closing\n\nBy default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.\n\nTo keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.\n\nYou can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-deny-close\">\n  This drawer will not close when you click on the overlay.\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-deny-close');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n\n  // Prevent the drawer from closing when the user clicks on the overlay\n  drawer.addEventListener('sl-request-close', event => {\n    if (event.detail.source === 'overlay') {\n      event.preventDefault();\n    }\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  // Prevent the drawer from closing when the user clicks on the overlay\n  function handleRequestClose(event) {\n    if (event.detail.source === 'overlay') {\n      event.preventDefault();\n    }\n  }\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlRequestClose={handleRequestClose} onSlAfterHide={() => setOpen(false)}>\n        This drawer will not close when you click on the overlay.\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Save &amp; Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n### Customizing Initial Focus\n\nBy default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.\n\n```html:preview\n<sl-drawer label=\"Drawer\" class=\"drawer-focus\">\n  <sl-input autofocus placeholder=\"I will have focus when the drawer is opened\"></sl-input>\n  <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n</sl-drawer>\n\n<sl-button>Open Drawer</sl-button>\n\n<script>\n  const drawer = document.querySelector('.drawer-focus');\n  const input = drawer.querySelector('sl-input');\n  const openButton = drawer.nextElementSibling;\n  const closeButton = drawer.querySelector('sl-button[variant=\"primary\"]');\n\n  openButton.addEventListener('click', () => drawer.show());\n  closeButton.addEventListener('click', () => drawer.hide());\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDrawer from '@shoelace-style/shoelace/dist/react/drawer';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlDrawer label=\"Drawer\" open={open} onSlAfterHide={() => setOpen(false)}>\n        <SlInput autofocus placeholder=\"I will have focus when the drawer is opened\" />\n        <SlButton slot=\"footer\" variant=\"primary\" onClick={() => setOpen(false)}>\n          Close\n        </SlButton>\n      </SlDrawer>\n\n      <SlButton onClick={() => setOpen(true)}>Open Drawer</SlButton>\n    </>\n  );\n};\n```\n\n:::tip\nYou can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.\n:::\n"
  },
  {
    "path": "docs/pages/components/dropdown.md",
    "content": "---\nmeta:\n  title: Dropdown\n  description: 'Dropdowns expose additional content that \"drops down\" in a panel.'\nlayout: component\n---\n\nDropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.\n\nDropdowns are designed to work well with [menus](/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/components/color-picker)). The API gives you complete control over showing, hiding, and positioning the panel.\n\n```html:preview\n<sl-dropdown>\n  <sl-button slot=\"trigger\" caret>Dropdown</sl-button>\n  <sl-menu>\n    <sl-menu-item>Dropdown Item 1</sl-menu-item>\n    <sl-menu-item>Dropdown Item 2</sl-menu-item>\n    <sl-menu-item>Dropdown Item 3</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item type=\"checkbox\" checked>Checkbox</sl-menu-item>\n    <sl-menu-item disabled>Disabled</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item>\n      Prefix\n      <sl-icon slot=\"prefix\" name=\"gift\"></sl-icon>\n    </sl-menu-item>\n    <sl-menu-item>\n      Suffix Icon\n      <sl-icon slot=\"suffix\" name=\"heart\"></sl-icon>\n    </sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlDropdown>\n    <SlButton slot=\"trigger\" caret>\n      Dropdown\n    </SlButton>\n    <SlMenu>\n      <SlMenuItem>Dropdown Item 1</SlMenuItem>\n      <SlMenuItem>Dropdown Item 2</SlMenuItem>\n      <SlMenuItem>Dropdown Item 3</SlMenuItem>\n      <SlDivider />\n      <SlMenuItem type=\"checkbox\" checked>\n        Checkbox\n      </SlMenuItem>\n      <SlMenuItem disabled>Disabled</SlMenuItem>\n      <SlDivider />\n      <SlMenuItem>\n        Prefix\n        <SlIcon slot=\"prefix\" name=\"gift\" />\n      </SlMenuItem>\n      <SlMenuItem>\n        Suffix Icon\n        <SlIcon slot=\"suffix\" name=\"heart\" />\n      </SlMenuItem>\n    </SlMenu>\n  </SlDropdown>\n);\n```\n\n## Examples\n\n### Getting the Selected Item\n\nWhen dropdowns are used with [menus](/components/menu), you can listen for the [`sl-select`](/components/menu#events) event to determine which menu item was selected. The menu item element will be exposed in `event.detail.item`. You can set `value` props to make it easier to identify commands.\n\n```html:preview\n<div class=\"dropdown-selection\">\n  <sl-dropdown>\n    <sl-button slot=\"trigger\" caret>Edit</sl-button>\n    <sl-menu>\n      <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n      <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n      <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n</div>\n\n<script>\n  const container = document.querySelector('.dropdown-selection');\n  const dropdown = container.querySelector('sl-dropdown');\n\n  dropdown.addEventListener('sl-select', event => {\n    const selectedItem = event.detail.item;\n    console.log(selectedItem.value);\n  });\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => {\n  function handleSelect(event) {\n    const selectedItem = event.detail.item;\n    console.log(selectedItem.value);\n  }\n\n  return (\n    <SlDropdown>\n      <SlButton slot=\"trigger\" caret>\n        Edit\n      </SlButton>\n      <SlMenu onSlSelect={handleSelect}>\n        <SlMenuItem value=\"cut\">Cut</SlMenuItem>\n        <SlMenuItem value=\"copy\">Copy</SlMenuItem>\n        <SlMenuItem value=\"paste\">Paste</SlMenuItem>\n      </SlMenu>\n    </SlDropdown>\n  );\n};\n```\n\nAlternatively, you can listen for the `click` event on individual menu items. Note that, using this approach, disabled menu items will still emit a `click` event.\n\n```html:preview\n<div class=\"dropdown-selection-alt\">\n  <sl-dropdown>\n    <sl-button slot=\"trigger\" caret>Edit</sl-button>\n    <sl-menu>\n      <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n      <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n      <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n</div>\n\n<script>\n  const container = document.querySelector('.dropdown-selection-alt');\n  const cut = container.querySelector('sl-menu-item[value=\"cut\"]');\n  const copy = container.querySelector('sl-menu-item[value=\"copy\"]');\n  const paste = container.querySelector('sl-menu-item[value=\"paste\"]');\n\n  cut.addEventListener('click', () => console.log('cut'));\n  copy.addEventListener('click', () => console.log('copy'));\n  paste.addEventListener('click', () => console.log('paste'));\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => {\n  function handleCut() {\n    console.log('cut');\n  }\n\n  function handleCopy() {\n    console.log('copy');\n  }\n\n  function handlePaste() {\n    console.log('paste');\n  }\n\n  return (\n    <SlDropdown>\n      <SlButton slot=\"trigger\" caret>\n        Edit\n      </SlButton>\n      <SlMenu>\n        <SlMenuItem onClick={handleCut}>Cut</SlMenuItem>\n        <SlMenuItem onClick={handleCopy}>Copy</SlMenuItem>\n        <SlMenuItem onClick={handlePaste}>Paste</SlMenuItem>\n      </SlMenu>\n    </SlDropdown>\n  );\n};\n```\n\n### Placement\n\nThe preferred placement of the dropdown can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport.\n\n```html:preview\n<sl-dropdown placement=\"top-start\">\n  <sl-button slot=\"trigger\" caret>Edit</sl-button>\n  <sl-menu>\n    <sl-menu-item>Cut</sl-menu-item>\n    <sl-menu-item>Copy</sl-menu-item>\n    <sl-menu-item>Paste</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item>Find</sl-menu-item>\n    <sl-menu-item>Replace</sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlDropdown placement=\"top-start\">\n    <SlButton slot=\"trigger\" caret>\n      Edit\n    </SlButton>\n    <SlMenu>\n      <SlMenuItem>Cut</SlMenuItem>\n      <SlMenuItem>Copy</SlMenuItem>\n      <SlMenuItem>Paste</SlMenuItem>\n      <SlDivider />\n      <SlMenuItem>Find</SlMenuItem>\n      <SlMenuItem>Replace</SlMenuItem>\n    </SlMenu>\n  </SlDropdown>\n);\n```\n\n### Distance\n\nThe distance from the panel to the trigger can be customized using the `distance` attribute. This value is specified in pixels.\n\n```html:preview\n<sl-dropdown distance=\"30\">\n  <sl-button slot=\"trigger\" caret>Edit</sl-button>\n  <sl-menu>\n    <sl-menu-item>Cut</sl-menu-item>\n    <sl-menu-item>Copy</sl-menu-item>\n    <sl-menu-item>Paste</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item>Find</sl-menu-item>\n    <sl-menu-item>Replace</sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlDropdown distance={30}>\n    <SlButton slot=\"trigger\" caret>\n      Edit\n    </SlButton>\n    <SlMenu>\n      <SlMenuItem>Cut</SlMenuItem>\n      <SlMenuItem>Copy</SlMenuItem>\n      <SlMenuItem>Paste</SlMenuItem>\n      <SlDivider />\n      <SlMenuItem>Find</SlMenuItem>\n      <SlMenuItem>Replace</SlMenuItem>\n    </SlMenu>\n  </SlDropdown>\n);\n```\n\n### Skidding\n\nThe offset of the panel along the trigger can be customized using the `skidding` attribute. This value is specified in pixels.\n\n```html:preview\n<sl-dropdown skidding=\"30\">\n  <sl-button slot=\"trigger\" caret>Edit</sl-button>\n  <sl-menu>\n    <sl-menu-item>Cut</sl-menu-item>\n    <sl-menu-item>Copy</sl-menu-item>\n    <sl-menu-item>Paste</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item>Find</sl-menu-item>\n    <sl-menu-item>Replace</sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlDropdown skidding={30}>\n    <SlButton slot=\"trigger\" caret>\n      Edit\n    </SlButton>\n    <SlMenu>\n      <SlMenuItem>Cut</SlMenuItem>\n      <SlMenuItem>Copy</SlMenuItem>\n      <SlMenuItem>Paste</SlMenuItem>\n      <SlDivider />\n      <SlMenuItem>Find</SlMenuItem>\n      <SlMenuItem>Replace</SlMenuItem>\n    </SlMenu>\n  </SlDropdown>\n);\n```\n\n### Submenus\n\nTo create a submenu, nest an `<sl-menu slot=\"submenu\">` element in a [menu item](/components/menu-item).\n\n```html:preview\n<sl-dropdown>\n  <sl-button slot=\"trigger\" caret>Edit</sl-button>\n\n  <sl-menu style=\"max-width: 200px;\">\n    <sl-menu-item value=\"undo\">Undo</sl-menu-item>\n    <sl-menu-item value=\"redo\">Redo</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n    <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n    <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n    <sl-divider></sl-divider>\n    <sl-menu-item>\n      Find\n      <sl-menu slot=\"submenu\">\n        <sl-menu-item value=\"find\">Find…</sl-menu-item>\n        <sl-menu-item value=\"find-previous\">Find Next</sl-menu-item>\n        <sl-menu-item value=\"find-next\">Find Previous</sl-menu-item>\n      </sl-menu>\n    </sl-menu-item>\n    <sl-menu-item>\n      Transformations\n      <sl-menu slot=\"submenu\">\n        <sl-menu-item value=\"uppercase\">Make uppercase</sl-menu-item>\n        <sl-menu-item value=\"lowercase\">Make lowercase</sl-menu-item>\n        <sl-menu-item value=\"capitalize\">Capitalize</sl-menu-item>\n      </sl-menu>\n    </sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst css = `\n  .dropdown-hoist {\n    border: solid 2px var(--sl-panel-border-color);\n    padding: var(--sl-spacing-medium);\n    overflow: hidden;\n  }\n`;\n\nconst App = () => (\n  <>\n    <SlDropdown>\n      <SlButton slot=\"trigger\" caret>Edit</SlButton>\n\n      <SlMenu style=\"max-width: 200px;\">\n        <SlMenuItem value=\"undo\">Undo</SlMenuItem>\n        <SlMenuItem value=\"redo\">Redo</SlMenuItem>\n        <SlDivider />\n        <SlMenuItem value=\"cut\">Cut</SlMenuItem>\n        <SlMenuItem value=\"copy\">Copy</SlMenuItem>\n        <SlMenuItem value=\"paste\">Paste</SlMenuItem>\n        <SlDivider />\n        <SlMenuItem>\n          Find\n          <SlMenu slot=\"submenu\">\n            <SlMenuItem value=\"find\">Find…</SlMenuItem>\n            <SlMenuItem value=\"find-previous\">Find Next</SlMenuItem>\n            <SlMenuItem value=\"find-next\">Find Previous</SlMenuItem>\n          </SlMenu>\n        </SlMenuItem>\n        <SlMenuItem>\n          Transformations\n          <SlMenu slot=\"submenu\">\n            <SlMenuItem value=\"uppercase\">Make uppercase</SlMenuItem>\n            <SlMenuItem value=\"lowercase\">Make lowercase</SlMenuItem>\n            <SlMenuItem value=\"capitalize\">Capitalize</SlMenuItem>\n          </SlMenu>\n        </SlMenuItem>\n      </SlMenu>\n    </SlDropdown>\n  </>\n);\n```\n\n:::warning\nAs a UX best practice, avoid using more than one level of submenu when possible.\n:::\n\n### Hoisting\n\nDropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.\n\n```html:preview\n<div class=\"dropdown-hoist\">\n  <sl-dropdown>\n    <sl-button slot=\"trigger\" caret>No Hoist</sl-button>\n    <sl-menu>\n      <sl-menu-item>Item 1</sl-menu-item>\n      <sl-menu-item>Item 2</sl-menu-item>\n      <sl-menu-item>Item 3</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n\n  <sl-dropdown hoist>\n    <sl-button slot=\"trigger\" caret>Hoist</sl-button>\n    <sl-menu>\n      <sl-menu-item>Item 1</sl-menu-item>\n      <sl-menu-item>Item 2</sl-menu-item>\n      <sl-menu-item>Item 3</sl-menu-item>\n    </sl-menu>\n  </sl-dropdown>\n</div>\n\n<style>\n  .dropdown-hoist {\n    position: relative;\n    border: solid 2px var(--sl-panel-border-color);\n    padding: var(--sl-spacing-medium);\n    overflow: hidden;\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst css = `\n  .dropdown-hoist {\n    border: solid 2px var(--sl-panel-border-color);\n    padding: var(--sl-spacing-medium);\n    overflow: hidden;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"dropdown-hoist\">\n      <SlDropdown>\n        <SlButton slot=\"trigger\" caret>\n          No Hoist\n        </SlButton>\n        <SlMenu>\n          <SlMenuItem>Item 1</SlMenuItem>\n          <SlMenuItem>Item 2</SlMenuItem>\n          <SlMenuItem>Item 3</SlMenuItem>\n        </SlMenu>\n      </SlDropdown>\n\n      <SlDropdown hoist>\n        <SlButton slot=\"trigger\" caret>\n          Hoist\n        </SlButton>\n        <SlMenu>\n          <SlMenuItem>Item 1</SlMenuItem>\n          <SlMenuItem>Item 2</SlMenuItem>\n          <SlMenuItem>Item 3</SlMenuItem>\n        </SlMenu>\n      </SlDropdown>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/format-bytes.md",
    "content": "---\nmeta:\n  title: Format Bytes\n  description: Formats a number as a human readable bytes value.\nlayout: component\n---\n\n```html:preview\n<div class=\"format-bytes-overview\">\n  The file is <sl-format-bytes value=\"1000\"></sl-format-bytes> in size. <br /><br />\n  <sl-input type=\"number\" value=\"1000\" label=\"Number to Format\" style=\"max-width: 180px;\"></sl-input>\n</div>\n\n<script>\n  const container = document.querySelector('.format-bytes-overview');\n  const formatter = container.querySelector('sl-format-bytes');\n  const input = container.querySelector('sl-input');\n\n  input.addEventListener('sl-input', () => (formatter.value = input.value || 0));\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  const [value, setValue] = useState(1000);\n\n  return (\n    <>\n      The file is <SlFormatBytes value={value} /> in size.\n      <br />\n      <br />\n      <SlInput\n        type=\"number\"\n        value={value}\n        label=\"Number to Format\"\n        style={{ maxWidth: '180px' }}\n        onSlInput={event => setValue(event.target.value)}\n      />\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n## Examples\n\n### Formatting Bytes\n\nSet the `value` attribute to a number to get the value in bytes.\n\n```html:preview\n<sl-format-bytes value=\"12\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000000\"></sl-format-bytes>\n```\n\n```jsx:react\nimport SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';\n\nconst App = () => (\n  <>\n    <SlFormatBytes value=\"12\" />\n    <br />\n    <SlFormatBytes value=\"1200\" />\n    <br />\n    <SlFormatBytes value=\"1200000\" />\n    <br />\n    <SlFormatBytes value=\"1200000000\" />\n  </>\n);\n```\n\n### Formatting Bits\n\nTo get the value in bits, set the `unit` attribute to `bit`.\n\n```html:preview\n<sl-format-bytes value=\"12\" unit=\"bit\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200\" unit=\"bit\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000\" unit=\"bit\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000000\" unit=\"bit\"></sl-format-bytes>\n```\n\n```jsx:react\nimport SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';\n\nconst App = () => (\n  <>\n    <SlFormatBytes value=\"12\" unit=\"bit\" />\n    <br />\n    <SlFormatBytes value=\"1200\" unit=\"bit\" />\n    <br />\n    <SlFormatBytes value=\"1200000\" unit=\"bit\" />\n    <br />\n    <SlFormatBytes value=\"1200000000\" unit=\"bit\" />\n  </>\n);\n```\n\n### Localization\n\nUse the `lang` attribute to set the number formatting locale.\n\n```html:preview\n<sl-format-bytes value=\"12\" lang=\"de\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200\" lang=\"de\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000\" lang=\"de\"></sl-format-bytes><br />\n<sl-format-bytes value=\"1200000000\" lang=\"de\"></sl-format-bytes>\n```\n\n```jsx:react\nimport SlFormatBytes from '@shoelace-style/shoelace/dist/react/format-bytes';\n\nconst App = () => (\n  <>\n    <SlFormatBytes value=\"12\" lang=\"de\" />\n    <br />\n    <SlFormatBytes value=\"1200\" lang=\"de\" />\n    <br />\n    <SlFormatBytes value=\"1200000\" lang=\"de\" />\n    <br />\n    <SlFormatBytes value=\"1200000000\" lang=\"de\" />\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/format-date.md",
    "content": "---\nmeta:\n  title: Format Date\n  description: Formats a date/time using the specified locale and options.\nlayout: component\n---\n\nLocalization is handled by the browser's [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). No language packs are required.\n\n```html:preview\n<!-- Shoelace 2 release date 🎉 -->\n<sl-format-date date=\"2020-07-15T09:17:00-04:00\"></sl-format-date>\n```\n\n```jsx:react\nimport SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';\n\nconst App = () => <SlFormatDate date=\"2020-07-15T09:17:00-04:00\" />;\n```\n\nThe `date` attribute determines the date/time to use when formatting. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript. If omitted, the current date/time will be assumed.\n\n:::tip\nWhen using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.\n:::\n\n## Examples\n\n### Date & Time Formatting\n\nFormatting options are based on those found in the [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). When formatting options are provided, the date/time will be formatted according to those values. When no formatting options are provided, a localized, numeric date will be displayed instead.\n\n```html:preview\n<!-- Human-readable date -->\n<sl-format-date month=\"long\" day=\"numeric\" year=\"numeric\"></sl-format-date><br />\n\n<!-- Time -->\n<sl-format-date hour=\"numeric\" minute=\"numeric\"></sl-format-date><br />\n\n<!-- Weekday -->\n<sl-format-date weekday=\"long\"></sl-format-date><br />\n\n<!-- Month -->\n<sl-format-date month=\"long\"></sl-format-date><br />\n\n<!-- Year -->\n<sl-format-date year=\"numeric\"></sl-format-date><br />\n\n<!-- No formatting options -->\n<sl-format-date></sl-format-date>\n```\n\n```jsx:react\nimport SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';\n\nconst App = () => (\n  <>\n    {/* Human-readable date */}\n    <SlFormatDate month=\"long\" day=\"numeric\" year=\"numeric\" />\n    <br />\n\n    {/* Time */}\n    <SlFormatDate hour=\"numeric\" minute=\"numeric\" />\n    <br />\n\n    {/* Weekday */}\n    <SlFormatDate weekday=\"long\" />\n    <br />\n\n    {/* Month */}\n    <SlFormatDate month=\"long\" />\n    <br />\n\n    {/* Year */}\n    <SlFormatDate year=\"numeric\" />\n    <br />\n\n    {/* No formatting options */}\n    <SlFormatDate />\n  </>\n);\n```\n\n### Hour Formatting\n\nBy default, the browser will determine whether to use 12-hour or 24-hour time. To force one or the other, set the `hour-format` attribute to `12` or `24`.\n\n```html:preview\n<sl-format-date hour=\"numeric\" minute=\"numeric\" hour-format=\"12\"></sl-format-date><br />\n<sl-format-date hour=\"numeric\" minute=\"numeric\" hour-format=\"24\"></sl-format-date>\n```\n\n```jsx:react\nimport SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';\n\nconst App = () => (\n  <>\n    <SlFormatDate hour=\"numeric\" minute=\"numeric\" hour-format=\"12\" />\n    <br />\n    <SlFormatDate hour=\"numeric\" minute=\"numeric\" hour-format=\"24\" />\n  </>\n);\n```\n\n### Localization\n\nUse the `lang` attribute to set the date/time formatting locale.\n\n```html:preview\nEnglish: <sl-format-date lang=\"en\"></sl-format-date><br />\nFrench: <sl-format-date lang=\"fr\"></sl-format-date><br />\nRussian: <sl-format-date lang=\"ru\"></sl-format-date>\n```\n\n```jsx:react\nimport SlFormatDate from '@shoelace-style/shoelace/dist/react/format-date';\n\nconst App = () => (\n  <>\n    English: <SlFormatDate lang=\"en\" />\n    <br />\n    French: <SlFormatDate lang=\"fr\" />\n    <br />\n    Russian: <SlFormatDate lang=\"ru\" />\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/format-number.md",
    "content": "---\nmeta:\n  title: Format Number\n  description: Formats a number using the specified locale and options.\nlayout: component\n---\n\nLocalization is handled by the browser's [`Intl.NumberFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat). No language packs are required.\n\n```html:preview\n<div class=\"format-number-overview\">\n  <sl-format-number value=\"1000\"></sl-format-number>\n  <br /><br />\n  <sl-input type=\"number\" value=\"1000\" label=\"Number to Format\" style=\"max-width: 180px;\"></sl-input>\n</div>\n\n<script>\n  const container = document.querySelector('.format-number-overview');\n  const formatter = container.querySelector('sl-format-number');\n  const input = container.querySelector('sl-input');\n\n  input.addEventListener('sl-input', () => (formatter.value = input.value || 0));\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  const [value, setValue] = useState(1000);\n\n  return (\n    <>\n      <SlFormatNumber value={value} />\n      <br />\n      <br />\n      <SlInput\n        type=\"number\"\n        value={value}\n        label=\"Number to Format\"\n        style={{ maxWidth: '180px' }}\n        onSlInput={event => setValue(event.target.value)}\n      />\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n## Examples\n\n### Percentages\n\nTo get the value as a percent, set the `type` attribute to `percent`.\n\n```html:preview\n<sl-format-number type=\"percent\" value=\"0\"></sl-format-number><br />\n<sl-format-number type=\"percent\" value=\"0.25\"></sl-format-number><br />\n<sl-format-number type=\"percent\" value=\"0.50\"></sl-format-number><br />\n<sl-format-number type=\"percent\" value=\"0.75\"></sl-format-number><br />\n<sl-format-number type=\"percent\" value=\"1\"></sl-format-number>\n```\n\n```jsx:react\nimport SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';\n\nconst App = () => (\n  <>\n    <SlFormatNumber type=\"percent\" value={0} />\n    <br />\n    <SlFormatNumber type=\"percent\" value={0.25} />\n    <br />\n    <SlFormatNumber type=\"percent\" value={0.5} />\n    <br />\n    <SlFormatNumber type=\"percent\" value={0.75} />\n    <br />\n    <SlFormatNumber type=\"percent\" value={1} />\n  </>\n);\n```\n\n### Localization\n\nUse the `lang` attribute to set the number formatting locale.\n\n```html:preview\nEnglish: <sl-format-number value=\"2000\" lang=\"en\" minimum-fraction-digits=\"2\"></sl-format-number><br />\nGerman: <sl-format-number value=\"2000\" lang=\"de\" minimum-fraction-digits=\"2\"></sl-format-number><br />\nRussian: <sl-format-number value=\"2000\" lang=\"ru\" minimum-fraction-digits=\"2\"></sl-format-number>\n```\n\n```jsx:react\nimport SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';\n\nconst App = () => (\n  <>\n    English: <SlFormatNumber value=\"2000\" lang=\"en\" minimum-fraction-digits=\"2\" />\n    <br />\n    German: <SlFormatNumber value=\"2000\" lang=\"de\" minimum-fraction-digits=\"2\" />\n    <br />\n    Russian: <SlFormatNumber value=\"2000\" lang=\"ru\" minimum-fraction-digits=\"2\" />\n  </>\n);\n```\n\n### Currency\n\nTo format a number as a monetary value, set the `type` attribute to `currency` and set the `currency` attribute to the desired ISO 4217 currency code. You should also specify `lang` to ensure the the number is formatted correctly for the target locale.\n\n```html:preview\n<sl-format-number type=\"currency\" currency=\"USD\" value=\"2000\" lang=\"en-US\"></sl-format-number><br />\n<sl-format-number type=\"currency\" currency=\"GBP\" value=\"2000\" lang=\"en-GB\"></sl-format-number><br />\n<sl-format-number type=\"currency\" currency=\"EUR\" value=\"2000\" lang=\"de\"></sl-format-number><br />\n<sl-format-number type=\"currency\" currency=\"RUB\" value=\"2000\" lang=\"ru\"></sl-format-number><br />\n<sl-format-number type=\"currency\" currency=\"CNY\" value=\"2000\" lang=\"zh-cn\"></sl-format-number>\n```\n\n```jsx:react\nimport SlFormatNumber from '@shoelace-style/shoelace/dist/react/format-number';\n\nconst App = () => (\n  <>\n    <SlFormatNumber type=\"currency\" currency=\"USD\" value=\"2000\" lang=\"en-US\" />\n    <br />\n    <SlFormatNumber type=\"currency\" currency=\"GBP\" value=\"2000\" lang=\"en-GB\" />\n    <br />\n    <SlFormatNumber type=\"currency\" currency=\"EUR\" value=\"2000\" lang=\"de\" />\n    <br />\n    <SlFormatNumber type=\"currency\" currency=\"RUB\" value=\"2000\" lang=\"ru\" />\n    <br />\n    <SlFormatNumber type=\"currency\" currency=\"CNY\" value=\"2000\" lang=\"zh-cn\" />\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/icon-button.md",
    "content": "---\nmeta:\n  title: Icon Button\n  description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.\nlayout: component\n---\n\nFor a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon).\n\n```html:preview\n<sl-icon-button name=\"gear\" label=\"Settings\"></sl-icon-button>\n```\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => <SlIconButton name=\"gear\" label=\"Settings\" />;\n```\n\n## Examples\n\n### Sizes\n\nIcon buttons inherit their parent element's `font-size`.\n\n```html:preview\n<sl-icon-button name=\"pencil\" label=\"Edit\" style=\"font-size: 1.5rem;\"></sl-icon-button>\n<sl-icon-button name=\"pencil\" label=\"Edit\" style=\"font-size: 2rem;\"></sl-icon-button>\n<sl-icon-button name=\"pencil\" label=\"Edit\" style=\"font-size: 2.5rem;\"></sl-icon-button>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => (\n  <>\n    <SlIconButton name=\"pencil\" label=\"Edit\" style={{ fontSize: '1.5rem' }} />\n    <SlIconButton name=\"pencil\" label=\"Edit\" style={{ fontSize: '2rem' }} />\n    <SlIconButton name=\"pencil\" label=\"Edit\" style={{ fontSize: '2.5rem' }} />\n  </>\n);\n```\n\n{% endraw %}\n\n### Colors\n\nIcon buttons are designed to have a uniform appearance, so their color is not inherited. However, you can still customize them by styling the `base` part.\n\n```html:preview\n<div class=\"icon-button-color\">\n  <sl-icon-button name=\"type-bold\" label=\"Bold\"></sl-icon-button>\n  <sl-icon-button name=\"type-italic\" label=\"Italic\"></sl-icon-button>\n  <sl-icon-button name=\"type-underline\" label=\"Underline\"></sl-icon-button>\n</div>\n\n<style>\n  .icon-button-color sl-icon-button::part(base) {\n    color: #b00091;\n  }\n\n  .icon-button-color sl-icon-button::part(base):hover,\n  .icon-button-color sl-icon-button::part(base):focus {\n    color: #c913aa;\n  }\n\n  .icon-button-color sl-icon-button::part(base):active {\n    color: #960077;\n  }\n</style>\n```\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst css = `\n  .icon-button-color sl-icon-button::part(base) {\n    color: #b00091;\n  }\n\n  .icon-button-color sl-icon-button::part(base):hover,\n  .icon-button-color sl-icon-button::part(base):focus {\n    color: #c913aa;\n  }\n\n  .icon-button-color sl-icon-button::part(base):active {\n    color: #960077;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"icon-button-color\">\n      <SlIconButton name=\"type-bold\" label=\"Bold\" />\n      <SlIconButton name=\"type-italic\" label=\"Italic\" />\n      <SlIconButton name=\"type-underline\" label=\"Underline\" />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Link Buttons\n\nUse the `href` attribute to convert the button to a link.\n\n```html:preview\n<sl-icon-button name=\"gear\" label=\"Settings\" href=\"https://example.com\" target=\"_blank\"></sl-icon-button>\n```\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => <SlIconButton name=\"gear\" label=\"Settings\" href=\"https://example.com\" target=\"_blank\" />;\n```\n\n### Icon Button with Tooltip\n\nWrap a tooltip around an icon button to provide contextual information to the user.\n\n```html:preview\n<sl-tooltip content=\"Settings\">\n  <sl-icon-button name=\"gear\" label=\"Settings\"></sl-icon-button>\n</sl-tooltip>\n```\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <SlTooltip content=\"Settings\">\n    <SlIconButton name=\"gear\" label=\"Settings\" />\n  </SlTooltip>\n);\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable the icon button.\n\n```html:preview\n<sl-icon-button name=\"gear\" label=\"Settings\" disabled></sl-icon-button>\n```\n\n```jsx:react\nimport SlIconButton from '@shoelace-style/shoelace/dist/react/icon-button';\n\nconst App = () => <SlIconButton name=\"gear\" label=\"Settings\" disabled />;\n```\n"
  },
  {
    "path": "docs/pages/components/icon.md",
    "content": "---\nmeta:\n  title: Icon\n  description: Icons are symbols that can be used to represent various options within an application.\nlayout: component\n---\n\nShoelace comes bundled with over 1,500 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These icons are part of the `default` icon library. If you prefer, you can register [custom icon libraries](#icon-libraries) as well.\n\n:::tip\nDepending on how you're loading Shoelace, you may need to copy icon assets and/or [set the base path](/getting-started/installation/#setting-the-base-path) so Shoelace knows where to load them from. Otherwise, icons may not appear and you'll see 404 Not Found errors in the dev console.\n:::\n\n## Default Icons\n\nAll available icons in the `default` icon library are shown below. Click or tap on any icon to copy its name, then you can use it in your HTML like this.\n\n```html\n<sl-icon name=\"icon-name-here\"></sl-icon>\n```\n\n<div class=\"icon-search\">\n  <div class=\"icon-search-controls\">\n    <sl-input placeholder=\"Search Icons\" clearable>\n      <sl-icon slot=\"prefix\" name=\"search\"></sl-icon>\n    </sl-input>\n    <sl-select value=\"outline\">\n      <sl-option value=\"outline\">Outlined</sl-option>\n      <sl-option value=\"fill\">Filled</sl-option>\n      <sl-option value=\"all\">All icons</sl-option>\n    </sl-select>\n  </div>\n  <div class=\"icon-list\"></div>\n  <input type=\"text\" class=\"icon-copy-input\" aria-hidden=\"true\" tabindex=\"-1\">\n</div>\n\n## Examples\n\n### Colors\n\nIcons inherit their color from the current text color. Thus, you can set the `color` property on the `<sl-icon>` element or an ancestor to change the color.\n\n```html:preview\n<div style=\"color: #4a90e2;\">\n  <sl-icon name=\"exclamation-triangle\"></sl-icon>\n  <sl-icon name=\"archive\"></sl-icon>\n  <sl-icon name=\"battery-charging\"></sl-icon>\n  <sl-icon name=\"bell\"></sl-icon>\n</div>\n<div style=\"color: #9013fe;\">\n  <sl-icon name=\"clock\"></sl-icon>\n  <sl-icon name=\"cloud\"></sl-icon>\n  <sl-icon name=\"download\"></sl-icon>\n  <sl-icon name=\"file-earmark\"></sl-icon>\n</div>\n<div style=\"color: #417505;\">\n  <sl-icon name=\"flag\"></sl-icon>\n  <sl-icon name=\"heart\"></sl-icon>\n  <sl-icon name=\"image\"></sl-icon>\n  <sl-icon name=\"lightning\"></sl-icon>\n</div>\n<div style=\"color: #f5a623;\">\n  <sl-icon name=\"mic\"></sl-icon>\n  <sl-icon name=\"search\"></sl-icon>\n  <sl-icon name=\"star\"></sl-icon>\n  <sl-icon name=\"trash\"></sl-icon>\n</div>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <>\n    <div style={{ color: '#4a90e2' }}>\n      <SlIcon name=\"exclamation-triangle\"></SlIcon>\n      <SlIcon name=\"archive\"></SlIcon>\n      <SlIcon name=\"battery-charging\"></SlIcon>\n      <SlIcon name=\"bell\"></SlIcon>\n    </div>\n    <div style={{ color: '#9013fe' }}>\n      <SlIcon name=\"clock\"></SlIcon>\n      <SlIcon name=\"cloud\"></SlIcon>\n      <SlIcon name=\"download\"></SlIcon>\n      <SlIcon name=\"file-earmark\"></SlIcon>\n    </div>\n    <div style={{ color: '#417505' }}>\n      <SlIcon name=\"flag\"></SlIcon>\n      <SlIcon name=\"heart\"></SlIcon>\n      <SlIcon name=\"image\"></SlIcon>\n      <SlIcon name=\"lightning\"></SlIcon>\n    </div>\n    <div style={{ color: '#f5a623' }}>\n      <SlIcon name=\"mic\"></SlIcon>\n      <SlIcon name=\"search\"></SlIcon>\n      <SlIcon name=\"star\"></SlIcon>\n      <SlIcon name=\"trash\"></SlIcon>\n    </div>\n  </>\n);\n```\n\n{% endraw %}\n\n### Sizing\n\nIcons are sized relative to the current font size. To change their size, set the `font-size` property on the icon itself or on a parent element as shown below.\n\n```html:preview\n<div style=\"font-size: 32px;\">\n  <sl-icon name=\"exclamation-triangle\"></sl-icon>\n  <sl-icon name=\"archive\"></sl-icon>\n  <sl-icon name=\"battery-charging\"></sl-icon>\n  <sl-icon name=\"bell\"></sl-icon>\n  <sl-icon name=\"clock\"></sl-icon>\n  <sl-icon name=\"cloud\"></sl-icon>\n  <sl-icon name=\"download\"></sl-icon>\n  <sl-icon name=\"file-earmark\"></sl-icon>\n  <sl-icon name=\"flag\"></sl-icon>\n  <sl-icon name=\"heart\"></sl-icon>\n  <sl-icon name=\"image\"></sl-icon>\n  <sl-icon name=\"lightning\"></sl-icon>\n  <sl-icon name=\"mic\"></sl-icon>\n  <sl-icon name=\"search\"></sl-icon>\n  <sl-icon name=\"star\"></sl-icon>\n  <sl-icon name=\"trash\"></sl-icon>\n</div>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <div style={{ fontSize: '32px' }}>\n    <SlIcon name=\"exclamation-triangle\" />\n    <SlIcon name=\"archive\" />\n    <SlIcon name=\"battery-charging\" />\n    <SlIcon name=\"bell\" />\n    <SlIcon name=\"clock\" />\n    <SlIcon name=\"cloud\" />\n    <SlIcon name=\"download\" />\n    <SlIcon name=\"file-earmark\" />\n    <SlIcon name=\"flag\" />\n    <SlIcon name=\"heart\" />\n    <SlIcon name=\"image\" />\n    <SlIcon name=\"lightning\" />\n    <SlIcon name=\"mic\" />\n    <SlIcon name=\"search\" />\n    <SlIcon name=\"star\" />\n    <SlIcon name=\"trash\" />\n  </div>\n);\n```\n\n{% endraw %}\n\n### Labels\n\nFor non-decorative icons, use the `label` attribute to announce it to assistive devices.\n\n```html:preview\n<sl-icon name=\"star-fill\" label=\"Add to favorites\"></sl-icon>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => <SlIcon name=\"star-fill\" label=\"Add to favorites\" />;\n```\n\n### Custom Icons\n\nCustom icons can be loaded individually with the `src` attribute. Only SVGs on a local or CORS-enabled endpoint are supported. If you're using more than one custom icon, it might make sense to register a [custom icon library](#icon-libraries).\n\n```html:preview\n<sl-icon src=\"https://shoelace.style/assets/images/shoe.svg\" style=\"font-size: 8rem;\"></sl-icon>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => <SlIcon src=\"https://shoelace.style/assets/images/shoe.svg\" style={{ fontSize: '8rem' }}></SlIcon>;\n```\n\n{% endraw %}\n\n## Icon Libraries\n\nYou can register additional icons to use with the `<sl-icon>` component through icon libraries. Icon files can exist locally or on a CORS-enabled endpoint (e.g. a CDN). There is no limit to how many icon libraries you can register and there is no cost associated with registering them, as individual icons are only requested when they're used.\n\nShoelace ships with two built-in icon libraries, `default` and `system`. The [default icon library](#customizing-the-default-library) contains all of the icons in the Bootstrap Icons project. The [system icon library](#customizing-the-system-library) contains only a small subset of icons that are used internally by Shoelace components.\n\nTo register an additional icon library, use the `registerIconLibrary()` function that's exported from `utilities/icon-library.js`. At a minimum, you must provide a name and a resolver function. The resolver function translates an icon name to a URL where the corresponding SVG file exists. Refer to the examples below to better understand how it works.\n\nIf necessary, a mutator function can be used to mutate the SVG element before rendering. This is necessary for some libraries due to the many possible ways SVGs are crafted. For example, icons should ideally inherit the current text color via `currentColor`, so you may need to apply `fill=\"currentColor` or `stroke=\"currentColor\"` to the SVG element using this function.\n\nHere's an example that registers an icon library located in the `/assets/icons` directory.\n\n```html\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('my-icons', {\n    resolver: name => `/assets/icons/${name}.svg`,\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n```\n\nTo display an icon, set the `library` and `name` attributes of an `<sl-icon>` element.\n\n```html\n<!-- This will show the icon located at /assets/icons/smile.svg -->\n<sl-icon library=\"my-icons\" name=\"smile\"></sl-icon>\n```\n\nIf an icon is used before registration occurs, it will be empty initially but shown when registered.\n\nThe following examples demonstrate how to register a number of popular, open source icon libraries via CDN. Feel free to adapt the code as you see fit to use your own origin or naming conventions.\n\n### Boxicons\n\nThis will register the [Boxicons](https://boxicons.com/) library using the jsDelivr CDN. This library has three variations: regular (`bx-*`), solid (`bxs-*`), and logos (`bxl-*`). A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [Creative Commons 4.0 License](https://github.com/atisawd/boxicons#license).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('boxicons', {\n    resolver: name => {\n      let folder = 'regular';\n      if (name.substring(0, 4) === 'bxs-') folder = 'solid';\n      if (name.substring(0, 4) === 'bxl-') folder = 'logos';\n      return `https://cdn.jsdelivr.net/npm/boxicons@2.0.5/svg/${folder}/${name}.svg`;\n    },\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"boxicons\" name=\"bx-bot\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bx-cookie\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bx-joystick\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bx-save\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bx-server\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bx-wine\"></sl-icon>\n  <br />\n  <sl-icon library=\"boxicons\" name=\"bxs-bot\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxs-cookie\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxs-joystick\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxs-save\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxs-server\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxs-wine\"></sl-icon>\n  <br />\n  <sl-icon library=\"boxicons\" name=\"bxl-apple\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxl-chrome\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxl-edge\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxl-firefox\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxl-opera\"></sl-icon>\n  <sl-icon library=\"boxicons\" name=\"bxl-microsoft\"></sl-icon>\n</div>\n```\n\n### Lucide\n\nThis will register the [Lucide](https://lucide.dev/) icon library using the jsDelivr CDN. This project is a community-maintained fork of the popular [Feather](https://feathericons.com/) icon library.\n\nIcons in this library are licensed under the [MIT License](https://github.com/lucide-icons/lucide/blob/master/LICENSE).\n\n```html:preview\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"lucide\" name=\"feather\"></sl-icon>\n  <sl-icon library=\"lucide\" name=\"pie-chart\"></sl-icon>\n  <sl-icon library=\"lucide\" name=\"settings\"></sl-icon>\n  <sl-icon library=\"lucide\" name=\"map-pin\"></sl-icon>\n  <sl-icon library=\"lucide\" name=\"printer\"></sl-icon>\n  <sl-icon library=\"lucide\" name=\"shopping-cart\"></sl-icon>\n</div>\n\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('lucide', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/lucide-static@0.16.29/icons/${name}.svg`\n  });\n</script>\n```\n\n### Font Awesome\n\nThis will register the [Font Awesome Free](https://fontawesome.com/) library using the jsDelivr CDN. This library has three variations: regular (`far-*`), solid (`fas-*`), and brands (`fab-*`). A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [Font Awesome Free License](https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt). Some of the icons that appear on the Font Awesome website require a license and are therefore not available in the CDN.\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('fa', {\n    resolver: name => {\n      const filename = name.replace(/^fa[rbs]-/, '');\n      let folder = 'regular';\n      if (name.substring(0, 4) === 'fas-') folder = 'solid';\n      if (name.substring(0, 4) === 'fab-') folder = 'brands';\n      return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.1/svgs/${folder}/${filename}.svg`;\n    },\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"fa\" name=\"far-bell\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"far-comment\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"far-hand-point-right\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"far-hdd\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"far-heart\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"far-star\"></sl-icon>\n  <br />\n  <sl-icon library=\"fa\" name=\"fas-archive\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fas-book\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fas-chess-knight\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fas-dice\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fas-pizza-slice\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fas-scroll\"></sl-icon>\n  <br />\n  <sl-icon library=\"fa\" name=\"fab-apple\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fab-chrome\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fab-edge\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fab-firefox\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fab-opera\"></sl-icon>\n  <sl-icon library=\"fa\" name=\"fab-microsoft\"></sl-icon>\n</div>\n```\n\n### Heroicons\n\nThis will register the [Heroicons](https://heroicons.com/) library using the jsDelivr CDN.\n\nIcons in this library are licensed under the [MIT License](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('heroicons', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/heroicons@2.0.1/24/outline/${name}.svg`\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"heroicons\" name=\"chat-bubble-left\"></sl-icon>\n  <sl-icon library=\"heroicons\" name=\"cloud\"></sl-icon>\n  <sl-icon library=\"heroicons\" name=\"cog\"></sl-icon>\n  <sl-icon library=\"heroicons\" name=\"document-text\"></sl-icon>\n  <sl-icon library=\"heroicons\" name=\"gift\"></sl-icon>\n  <sl-icon library=\"heroicons\" name=\"speaker-wave\"></sl-icon>\n</div>\n```\n\n### Iconoir\n\nThis will register the [Iconoir](https://iconoir.com/) library using the jsDelivr CDN.\n\nIcons in this library are licensed under the [MIT License](https://github.com/lucaburgio/iconoir/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('iconoir', {\n    resolver: name => `https://cdn.jsdelivr.net/gh/lucaburgio/iconoir@latest/icons/${name}.svg`\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"iconoir\" name=\"check-circled-outline\"></sl-icon>\n  <sl-icon library=\"iconoir\" name=\"drawer\"></sl-icon>\n  <sl-icon library=\"iconoir\" name=\"keyframes\"></sl-icon>\n  <sl-icon library=\"iconoir\" name=\"headset-help\"></sl-icon>\n  <sl-icon library=\"iconoir\" name=\"color-picker\"></sl-icon>\n  <sl-icon library=\"iconoir\" name=\"wifi\"></sl-icon>\n</div>\n```\n\n### Ionicons\n\nThis will register the [Ionicons](https://ionicons.com/) library using the jsDelivr CDN. This library has three variations: outline (default), filled (`*-filled`), and sharp (`*-sharp`). A mutator function is required to polyfill a handful of styles we're not including.\n\nIcons in this library are licensed under the [MIT License](https://github.com/ionic-team/ionicons/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('ionicons', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/ionicons@5.1.2/dist/ionicons/svg/${name}.svg`,\n    mutator: svg => {\n      svg.setAttribute('fill', 'currentColor');\n      svg.setAttribute('stroke', 'currentColor');\n      [...svg.querySelectorAll('.ionicon-fill-none')].map(el => el.setAttribute('fill', 'none'));\n      [...svg.querySelectorAll('.ionicon-stroke-width')].map(el => el.setAttribute('stroke-width', '32px'));\n    }\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"ionicons\" name=\"alarm\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"american-football\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"bug\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"chatbubble\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"settings\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"warning\"></sl-icon>\n  <br />\n  <sl-icon library=\"ionicons\" name=\"alarm-outline\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"american-football-outline\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"bug-outline\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"chatbubble-outline\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"settings-outline\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"warning-outline\"></sl-icon>\n  <br />\n  <sl-icon library=\"ionicons\" name=\"alarm-sharp\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"american-football-sharp\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"bug-sharp\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"chatbubble-sharp\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"settings-sharp\"></sl-icon>\n  <sl-icon library=\"ionicons\" name=\"warning-sharp\"></sl-icon>\n</div>\n```\n\n### Jam Icons\n\nThis will register the [Jam Icons](https://jam-icons.com/) library using the jsDelivr CDN. This library has two variations: regular (default) and filled (`*-f`). A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [MIT License](https://github.com/michaelampr/jam/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('jam', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/jam-icons@2.0.0/svg/${name}.svg`,\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"jam\" name=\"calendar\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"camera\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"filter\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"leaf\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"picture\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"set-square\"></sl-icon>\n  <br />\n  <sl-icon library=\"jam\" name=\"calendar-f\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"camera-f\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"filter-f\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"leaf-f\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"picture-f\"></sl-icon>\n  <sl-icon library=\"jam\" name=\"set-square-f\"></sl-icon>\n</div>\n```\n\n### Material Icons\n\nThis will register the [Material Icons](https://material.io/resources/icons/?style=baseline) library using the jsDelivr CDN. This library has three variations: outline (default), round (`*_round`), and sharp (`*_sharp`). A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [Apache 2.0 License](https://github.com/google/material-design-icons/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('material', {\n    resolver: name => {\n      const match = name.match(/^(.*?)(_(round|sharp))?$/);\n      return `https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.5/svg/${match[1]}/${match[3] || 'outline'}.svg`;\n    },\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"material\" name=\"notifications\"></sl-icon>\n  <sl-icon library=\"material\" name=\"email\"></sl-icon>\n  <sl-icon library=\"material\" name=\"delete\"></sl-icon>\n  <sl-icon library=\"material\" name=\"volume_up\"></sl-icon>\n  <sl-icon library=\"material\" name=\"settings\"></sl-icon>\n  <sl-icon library=\"material\" name=\"shopping_basket\"></sl-icon>\n  <br />\n  <sl-icon library=\"material\" name=\"notifications_round\"></sl-icon>\n  <sl-icon library=\"material\" name=\"email_round\"></sl-icon>\n  <sl-icon library=\"material\" name=\"delete_round\"></sl-icon>\n  <sl-icon library=\"material\" name=\"volume_up_round\"></sl-icon>\n  <sl-icon library=\"material\" name=\"settings_round\"></sl-icon>\n  <sl-icon library=\"material\" name=\"shopping_basket_round\"></sl-icon>\n  <br />\n  <sl-icon library=\"material\" name=\"notifications_sharp\"></sl-icon>\n  <sl-icon library=\"material\" name=\"email_sharp\"></sl-icon>\n  <sl-icon library=\"material\" name=\"delete_sharp\"></sl-icon>\n  <sl-icon library=\"material\" name=\"volume_up_sharp\"></sl-icon>\n  <sl-icon library=\"material\" name=\"settings_sharp\"></sl-icon>\n  <sl-icon library=\"material\" name=\"shopping_basket_sharp\"></sl-icon>\n</div>\n```\n\n### Remix Icon\n\nThis will register the [Remix Icon](https://remixicon.com/) library using the jsDelivr CDN. This library groups icons by categories, so the name must include the category and icon separated by a slash, as well as the `-line` or `-fill` suffix as needed. A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [Apache 2.0 License](https://github.com/Remix-Design/RemixIcon/blob/master/License).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('remixicon', {\n    resolver: name => {\n      const match = name.match(/^(.*?)\\/(.*?)?$/);\n      match[1] = match[1].charAt(0).toUpperCase() + match[1].slice(1);\n      return `https://cdn.jsdelivr.net/npm/remixicon@2.5.0/icons/${match[1]}/${match[2]}.svg`;\n    },\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"remixicon\" name=\"business/cloud-line\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"design/brush-line\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"business/pie-chart-line\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"development/bug-line\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"media/image-line\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"system/alert-line\"></sl-icon>\n  <br />\n  <sl-icon library=\"remixicon\" name=\"business/cloud-fill\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"design/brush-fill\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"business/pie-chart-fill\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"development/bug-fill\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"media/image-fill\"></sl-icon>\n  <sl-icon library=\"remixicon\" name=\"system/alert-fill\"></sl-icon>\n</div>\n```\n\n### Tabler Icons\n\nThis will register the [Tabler Icons](https://tabler-icons.io/) library using the jsDelivr CDN. This library features over 1,950 open source icons.\n\nIcons in this library are licensed under the [MIT License](https://github.com/tabler/tabler-icons/blob/master/LICENSE).\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('tabler', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/@tabler/icons@1.68.0/icons/${name}.svg`\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"tabler\" name=\"alert-triangle\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"arrow-back\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"at\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"ball-baseball\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"cake\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"files\"></sl-icon>\n  <br />\n  <sl-icon library=\"tabler\" name=\"keyboard\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"moon\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"pig\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"printer\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"ship\"></sl-icon>\n  <sl-icon library=\"tabler\" name=\"toilet-paper\"></sl-icon>\n</div>\n```\n\n### Unicons\n\nThis will register the [Unicons](https://iconscout.com/unicons) library using the jsDelivr CDN. This library has two variations: line (default) and solid (`*-s`). A mutator function is required to set the SVG's `fill` to `currentColor`.\n\nIcons in this library are licensed under the [Apache 2.0 License](https://github.com/Iconscout/unicons/blob/master/LICENSE). Some of the icons that appear on the Unicons website, particularly many of the solid variations, require a license and are therefore not available in the CDN.\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('unicons', {\n    resolver: name => {\n      const match = name.match(/^(.*?)(-s)?$/);\n      return `https://cdn.jsdelivr.net/npm/@iconscout/unicons@3.0.3/svg/${match[2] === '-s' ? 'solid' : 'line'}/${\n        match[1]\n      }.svg`;\n    },\n    mutator: svg => svg.setAttribute('fill', 'currentColor')\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"unicons\" name=\"clock\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"graph-bar\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"padlock\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"polygon\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"rocket\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"star\"></sl-icon>\n  <br />\n  <sl-icon library=\"unicons\" name=\"clock-s\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"graph-bar-s\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"padlock-s\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"polygon-s\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"rocket-s\"></sl-icon>\n  <sl-icon library=\"unicons\" name=\"star-s\"></sl-icon>\n</div>\n```\n\n### Customizing the Default Library\n\nThe default icon library contains over 1,300 icons courtesy of the [Bootstrap Icons](https://icons.getbootstrap.com/) project. These are the icons that display when you use `<sl-icon>` without the `library` attribute. If you prefer to have these icons resolve elsewhere or to a different icon library, register an icon library using the `default` name and a custom resolver.\n\nThis example will load the same set of icons from the jsDelivr CDN instead of your local assets folder.\n\n```html\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('default', {\n    resolver: name => `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.0.0/icons/${name}.svg`\n  });\n</script>\n```\n\n#### Customize the default library to use SVG sprites\n\nTo improve performance you can use a SVG sprites to avoid multiple trips for each SVG. The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using hash selector.\n\nAs always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.\n\n:::danger\nWhen using sprite sheets, the `sl-load` and `sl-error` events will not fire.\n:::\n\n:::danger\nFor security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<sl-icon>` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements. For this reason, sprite sheets should only be used if you're self-hosting them.\n:::\n\n```html:preview\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('sprite', {\n    resolver: name => `/assets/images/sprite.svg#${name}`,\n    mutator: svg => svg.setAttribute('fill', 'currentColor'),\n    spriteSheet: true\n  });\n</script>\n\n<div style=\"font-size: 24px;\">\n  <sl-icon library=\"sprite\" name=\"clock\"></sl-icon>\n  <sl-icon library=\"sprite\" name=\"speedometer\"></sl-icon>\n</div>\n```\n\n### Customizing the System Library\n\nThe system library contains only the icons used internally by Shoelace components. Unlike the default icon library, the system library does not rely on physical assets. Instead, its icons are hard-coded as data URIs into the resolver to ensure their availability.\n\nIf you want to change the icons Shoelace uses internally, you can register an icon library using the `system` name and a custom resolver. If you choose to do this, it's your responsibility to provide all of the icons that are required by components. You can reference `src/components/library.system.ts` for a complete list of system icons used by Shoelace.\n\n```html\n<script type=\"module\">\n  import { registerIconLibrary } from '/dist/utilities/icon-library.js';\n\n  registerIconLibrary('system', {\n    resolver: name => `/path/to/custom/icons/${name}.svg`\n  });\n</script>\n```\n\n<!-- Supporting scripts and styles for the search utility -->\n<script>\n  function wrapWithTooltip(item) {\n    const tooltip = document.createElement('sl-tooltip');\n    tooltip.content = item.getAttribute('data-name');\n\n    // Close open tooltips\n    document.querySelectorAll('.icon-list sl-tooltip[open]').forEach(tooltip => tooltip.hide());\n\n    // Wrap it with a tooltip and trick it into showing up\n    item.parentNode.insertBefore(tooltip, item);\n    tooltip.appendChild(item);\n    requestAnimationFrame(() => tooltip.dispatchEvent(new MouseEvent('mouseover')));\n  }\n\n  fetch('/dist/assets/icons/icons.json')\n    .then(res => res.json())\n    .then(icons => {\n      const container = document.querySelector('.icon-search');\n      const input = container.querySelector('sl-input');\n      const select = container.querySelector('sl-select');\n      const copyInput = container.querySelector('.icon-copy-input');\n      const loader = container.querySelector('.icon-loader');\n      const list = container.querySelector('.icon-list');\n      const queue = [];\n      let inputTimeout;\n\n      // Generate icons\n      icons.map(i => {\n        const item = document.createElement('div');\n        item.classList.add('icon-list-item');\n        item.setAttribute('data-name', i.name);\n        item.setAttribute('data-terms', [i.name, i.title, ...(i.tags || []), ...(i.categories || [])].join(' '));\n        item.innerHTML = `\n          <svg width=\"1em\" height=\"1em\" fill=\"currentColor\">\n            <use href=\"/assets/images/sprite.svg#${i.name}\"></use>\n          </svg>\n        `;\n        list.appendChild(item);\n\n        // Wrap it with a tooltip the first time the mouse lands on it. We do this instead of baking them into the DOM\n        // to improve this page's performance. See: https://github.com/shoelace-style/shoelace/issues/1122\n        item.addEventListener('mouseover', () => wrapWithTooltip(item), { once: true });\n\n        // Copy on click\n        item.addEventListener('click', () => {\n          const tooltip = item.closest('sl-tooltip');\n          copyInput.value = i.name;\n          copyInput.select();\n          document.execCommand('copy');\n\n          if (tooltip) {\n            tooltip.content = 'Copied!';\n            setTimeout(() => tooltip.content = i.name, 1000);\n          }\n        });\n      });\n\n      // Filter as the user types\n      input.addEventListener('sl-input', () => {\n        clearTimeout(inputTimeout);\n        inputTimeout = setTimeout(() => {\n          [...list.querySelectorAll('.icon-list-item')].map(item => {\n            const filter = input.value.toLowerCase();\n            if (filter === '') {\n              item.hidden = false;\n            } else {\n              const terms = item.getAttribute('data-terms').toLowerCase();\n              item.hidden = terms.indexOf(filter) < 0;\n            }\n          });\n        }, 250);\n      });\n\n      // Sort by type and remember preference\n      const iconType = sessionStorage.getItem('sl-icon:type') || 'outline';\n      select.value = iconType;\n      list.setAttribute('data-type', select.value);\n      select.addEventListener('sl-change', () => {\n        list.setAttribute('data-type', select.value);\n        sessionStorage.setItem('sl-icon:type', select.value);\n      });\n    });\n</script>\n\n<style>\n  .icon-search {\n    border: solid 1px var(--sl-panel-border-color);\n    border-radius: var(--sl-border-radius-medium);\n    padding: var(--sl-spacing-medium);\n  }\n\n  .icon-search [hidden] {\n    display: none;\n  }\n\n  .icon-search-controls {\n    display: flex;\n  }\n\n  .icon-search-controls sl-input {\n    flex: 1 1 auto;\n  }\n\n  .icon-search-controls sl-select {\n    width: 10rem;\n    flex: 0 0 auto;\n    margin-left: 1rem;\n  }\n\n  .icon-loader {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    min-height: 30vh;\n  }\n\n  .icon-list {\n    display: grid;\n    grid-template-columns: repeat(12, 1fr);\n    position: relative;\n    margin-top: 1rem;\n  }\n\n  .icon-loader[hidden],\n  .icon-list[hidden] {\n    display: none;\n  }\n\n  .icon-list-item {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: var(--sl-border-radius-medium);\n    font-size: 24px;\n    width: 2em;\n    height: 2em;\n    margin: 0 auto;\n    cursor: copy;\n    transition: var(--sl-transition-medium) all;\n  }\n\n  .icon-list-item:hover {\n    background-color: var(--sl-color-primary-50);\n    color: var(--sl-color-primary-600);\n  }\n\n  .icon-list[data-type=\"outline\"] .icon-list-item[data-name$=\"-fill\"] {\n    display: none;\n  }\n\n  .icon-list[data-type=\"fill\"] .icon-list-item:not([data-name$=\"-fill\"]) {\n    display: none;\n  }\n\n  .icon-copy-input {\n    position: absolute;\n    opacity: 0;\n    pointer-events: none;\n  }\n\n  @media screen and (max-width: 1000px) {\n    .icon-list {\n      grid-template-columns: repeat(8, 1fr);\n    }\n\n    .icon-list-item {\n      font-size: 20px;\n    }\n\n    .icon-search-controls {\n      display: block;\n    }\n\n    .icon-search-controls sl-select {\n      width: auto;\n      margin: 1rem 0 0 0;\n    }\n  }\n\n  @media screen and (max-width: 500px) {\n    .icon-list {\n      grid-template-columns: repeat(4, 1fr);\n    }\n  }\n</style>\n"
  },
  {
    "path": "docs/pages/components/image-comparer.md",
    "content": "---\nmeta:\n  title: Image Comparer\n  description: Compare visual differences between similar photos with a sliding panel.\nlayout: component\n---\n\nFor best results, use images that share the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.)\n\n```html:preview\n<sl-image-comparer>\n  <img\n    slot=\"before\"\n    src=\"https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5\"\n    alt=\"Grayscale version of kittens in a basket looking around.\"\n  />\n  <img\n    slot=\"after\"\n    src=\"https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80\"\n    alt=\"Color version of kittens in a basket looking around.\"\n  />\n</sl-image-comparer>\n```\n\n```jsx:react\nimport SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';\n\nconst App = () => (\n  <SlImageComparer>\n    <img\n      slot=\"before\"\n      src=\"https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&sat=-100&bri=-5\"\n      alt=\"Grayscale version of kittens in a basket looking around.\"\n    />\n    <img\n      slot=\"after\"\n      src=\"https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80\"\n      alt=\"Color version of kittens in a basket looking around.\"\n    />\n  </SlImageComparer>\n);\n```\n\n## Examples\n\n### Initial Position\n\nUse the `position` attribute to set the initial position of the slider. This is a percentage from `0` to `100`.\n\n```html:preview\n<sl-image-comparer position=\"25\">\n  <img\n    slot=\"before\"\n    src=\"https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80\"\n    alt=\"A person sitting on bricks wearing untied boots.\"\n  />\n  <img\n    slot=\"after\"\n    src=\"https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80\"\n    alt=\"A person sitting on a yellow curb tying shoelaces on a boot.\"\n  />\n</sl-image-comparer>\n```\n\n```jsx:react\nimport SlImageComparer from '@shoelace-style/shoelace/dist/react/image-comparer';\n\nconst App = () => (\n  <SlImageComparer position={25}>\n    <img\n      slot=\"before\"\n      src=\"https://images.unsplash.com/photo-1520903074185-8eca362b3dce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80\"\n      alt=\"A person sitting on bricks wearing untied boots.\"\n    />\n    <img\n      slot=\"after\"\n      src=\"https://images.unsplash.com/photo-1520640023173-50a135e35804?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80\"\n      alt=\"A person sitting on a yellow curb tying shoelaces on a boot.\"\n    />\n  </SlImageComparer>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/include.md",
    "content": "---\nmeta:\n  title: Include\n  description: Includes give you the power to embed external HTML files into the page.\nlayout: component\n---\n\nIncluded files are asynchronously requested using `window.fetch()`. Requests are cached, so the same file can be included multiple times, but only one request will be made.\n\nThe included content will be inserted into the `<sl-include>` element's default slot so it can be easily accessed and styled through the light DOM.\n\n```html:preview\n<sl-include src=\"https://shoelace.style/assets/examples/include.html\"></sl-include>\n```\n\n```jsx:react\nimport SlInclude from '@shoelace-style/shoelace/dist/react/include';\n\nconst App = () => <SlInclude src=\"https://shoelace.style/assets/examples/include.html\" />;\n```\n\n## Examples\n\n### Listening for Events\n\nWhen an include file loads successfully, the `sl-load` event will be emitted. You can listen for this event to add custom loading logic to your includes.\n\nIf the request fails, the `sl-error` event will be emitted. In this case, `event.detail.status` will contain the resulting HTTP status code of the request, e.g. 404 (not found).\n\n```html\n<sl-include src=\"https://shoelace.style/assets/examples/include.html\"></sl-include>\n\n<script>\n  const include = document.querySelector('sl-include');\n\n  include.addEventListener('sl-load', event => {\n    if (event.eventPhase === Event.AT_TARGET) {\n      console.log('Success');\n    }\n  });\n\n  include.addEventListener('sl-error', event => {\n    if (event.eventPhase === Event.AT_TARGET) {\n      console.log('Error', event.detail.status);\n    }\n  });\n</script>\n```\n"
  },
  {
    "path": "docs/pages/components/input.md",
    "content": "---\nmeta:\n  title: Input\n  description: Inputs collect data from the user.\nlayout: component\n---\n\n```html:preview\n<sl-input></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput />;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Labels\n\nUse the `label` attribute to give the input an accessible label. For labels that contain HTML, use the `label` slot instead.\n\n```html:preview\n<sl-input label=\"What is your name?\"></sl-input>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput label=\"What is your name?\" />;\n```\n\n### Help Text\n\nAdd descriptive help text to an input with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-input label=\"Nickname\" help-text=\"What would you like people to call you?\"></sl-input>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput label=\"Nickname\" help-text=\"What would you like people to call you?\" />;\n```\n\n### Placeholders\n\nUse the `placeholder` attribute to add a placeholder.\n\n```html:preview\n<sl-input placeholder=\"Type something\"></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput placeholder=\"Type something\" />;\n```\n\n### Clearable\n\nAdd the `clearable` attribute to add a clear button when the input has content.\n\n```html:preview\n<sl-input placeholder=\"Clearable\" clearable></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput placeholder=\"Clearable\" clearable />;\n```\n\n### Toggle Password\n\nAdd the `password-toggle` attribute to add a toggle button that will show the password when activated.\n\n```html:preview\n<sl-input type=\"password\" placeholder=\"Password Toggle\" password-toggle></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput type=\"password\" placeholder=\"Password Toggle\" size=\"medium\" password-toggle />;\n```\n\n### Filled Inputs\n\nAdd the `filled` attribute to draw a filled input.\n\n```html:preview\n<sl-input placeholder=\"Type something\" filled></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput placeholder=\"Type something\" filled />;\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable an input.\n\n```html:preview\n<sl-input placeholder=\"Disabled\" disabled></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => <SlInput placeholder=\"Disabled\" disabled />;\n```\n\n### Sizes\n\nUse the `size` attribute to change an input's size.\n\n```html:preview\n<sl-input placeholder=\"Small\" size=\"small\"></sl-input>\n<br />\n<sl-input placeholder=\"Medium\" size=\"medium\"></sl-input>\n<br />\n<sl-input placeholder=\"Large\" size=\"large\"></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => (\n  <>\n    <SlInput placeholder=\"Small\" size=\"small\" />\n    <br />\n    <SlInput placeholder=\"Medium\" size=\"medium\" />\n    <br />\n    <SlInput placeholder=\"Large\" size=\"large\" />\n  </>\n);\n```\n\n### Pill\n\nUse the `pill` attribute to give inputs rounded edges.\n\n```html:preview\n<sl-input placeholder=\"Small\" size=\"small\" pill></sl-input>\n<br />\n<sl-input placeholder=\"Medium\" size=\"medium\" pill></sl-input>\n<br />\n<sl-input placeholder=\"Large\" size=\"large\" pill></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => (\n  <>\n    <SlInput placeholder=\"Small\" size=\"small\" pill />\n    <br />\n    <SlInput placeholder=\"Medium\" size=\"medium\" pill />\n    <br />\n    <SlInput placeholder=\"Large\" size=\"large\" pill />\n  </>\n);\n```\n\n### Input Types\n\nThe `type` attribute controls the type of input the browser renders.\n\n```html:preview\n<sl-input type=\"email\" placeholder=\"Email\"></sl-input>\n<br />\n<sl-input type=\"number\" placeholder=\"Number\"></sl-input>\n<br />\n<sl-input type=\"date\" placeholder=\"Date\"></sl-input>\n```\n\n```jsx:react\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => (\n  <>\n    <SlInput type=\"email\" placeholder=\"Email\" />\n    <br />\n    <SlInput type=\"number\" placeholder=\"Number\" />\n    <br />\n    <SlInput type=\"date\" placeholder=\"Date\" />\n  </>\n);\n```\n\n### Prefix & Suffix Icons\n\nUse the `prefix` and `suffix` slots to add icons.\n\n```html:preview\n<sl-input placeholder=\"Small\" size=\"small\">\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-icon name=\"chat\" slot=\"suffix\"></sl-icon>\n</sl-input>\n<br />\n<sl-input placeholder=\"Medium\" size=\"medium\">\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-icon name=\"chat\" slot=\"suffix\"></sl-icon>\n</sl-input>\n<br />\n<sl-input placeholder=\"Large\" size=\"large\">\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-icon name=\"chat\" slot=\"suffix\"></sl-icon>\n</sl-input>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => (\n  <>\n    <SlInput placeholder=\"Small\" size=\"small\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlIcon name=\"chat\" slot=\"suffix\"></SlIcon>\n    </SlInput>\n    <br />\n    <SlInput placeholder=\"Medium\" size=\"medium\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlIcon name=\"chat\" slot=\"suffix\"></SlIcon>\n    </SlInput>\n    <br />\n    <SlInput placeholder=\"Large\" size=\"large\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlIcon name=\"chat\" slot=\"suffix\"></SlIcon>\n    </SlInput>\n  </>\n);\n```\n\n### Customizing Label Position\n\nUse [CSS parts](#css-parts) to customize the way form controls are drawn. This example uses CSS grid to position the label to the left of the control, but the possible orientations are nearly endless. The same technique works for inputs, textareas, radio groups, and similar form controls.\n\n```html:preview\n<sl-input class=\"label-on-left\" label=\"Name\" help-text=\"Enter your name\"></sl-input>\n<sl-input class=\"label-on-left\" label=\"Email\" type=\"email\" help-text=\"Enter your email\"></sl-input>\n<sl-textarea class=\"label-on-left\" label=\"Bio\" help-text=\"Tell us something about yourself\"></sl-textarea>\n\n<style>\n  .label-on-left {\n    --label-width: 3.75rem;\n    --gap-width: 1rem;\n  }\n\n  .label-on-left + .label-on-left {\n    margin-top: var(--sl-spacing-medium);\n  }\n\n  .label-on-left::part(form-control) {\n    display: grid;\n    grid: auto / var(--label-width) 1fr;\n    gap: var(--sl-spacing-3x-small) var(--gap-width);\n    align-items: center;\n  }\n\n  .label-on-left::part(form-control-label) {\n    text-align: right;\n  }\n\n  .label-on-left::part(form-control-help-text) {\n    grid-column-start: 2;\n  }\n</style>\n```\n"
  },
  {
    "path": "docs/pages/components/menu-item.md",
    "content": "---\nmeta:\n  title: Menu Item\n  description: Menu items provide options for the user to pick from in a menu.\nlayout: component\n---\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item>Option 1</sl-menu-item>\n  <sl-menu-item>Option 2</sl-menu-item>\n  <sl-menu-item>Option 3</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item type=\"checkbox\" checked>Checkbox</sl-menu-item>\n  <sl-menu-item disabled>Disabled</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item>\n    Prefix Icon\n    <sl-icon slot=\"prefix\" name=\"gift\"></sl-icon>\n  </sl-menu-item>\n  <sl-menu-item>\n    Suffix Icon\n    <sl-icon slot=\"suffix\" name=\"heart\"></sl-icon>\n  </sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem>Option 1</SlMenuItem>\n    <SlMenuItem>Option 2</SlMenuItem>\n    <SlMenuItem>Option 3</SlMenuItem>\n    <SlDivider />\n    <SlMenuItem type=\"checkbox\" checked>\n      Checkbox\n    </SlMenuItem>\n    <SlMenuItem disabled>Disabled</SlMenuItem>\n    <SlDivider />\n    <SlMenuItem>\n      Prefix Icon\n      <SlIcon slot=\"prefix\" name=\"gift\" />\n    </SlMenuItem>\n    <SlMenuItem>\n      Suffix Icon\n      <SlIcon slot=\"suffix\" name=\"heart\" />\n    </SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n## Examples\n\n### Prefix & Suffix\n\nAdd content to the start and end of menu items using the `prefix` and `suffix` slots.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item>\n    <sl-icon slot=\"prefix\" name=\"house\"></sl-icon>\n    Home\n  </sl-menu-item>\n\n  <sl-menu-item>\n    <sl-icon slot=\"prefix\" name=\"envelope\"></sl-icon>\n    Messages\n    <sl-badge slot=\"suffix\" variant=\"primary\" pill>12</sl-badge>\n  </sl-menu-item>\n\n  <sl-divider></sl-divider>\n\n  <sl-menu-item>\n    <sl-icon slot=\"prefix\" name=\"gear\"></sl-icon>\n    Settings\n  </sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlBadge from '@shoelace-style/shoelace/dist/react/badge';\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem>\n      <SlIcon slot=\"prefix\" name=\"house\" />\n      Home\n    </SlMenuItem>\n\n    <SlMenuItem>\n      <SlIcon slot=\"prefix\" name=\"envelope\" />\n      Messages\n      <SlBadge slot=\"suffix\" variant=\"primary\" pill>\n        12\n      </SlBadge>\n    </SlMenuItem>\n\n    <SlDivider />\n\n    <SlMenuItem>\n      <SlIcon slot=\"prefix\" name=\"gear\" />\n      Settings\n    </SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n### Disabled\n\nAdd the `disabled` attribute to disable the menu item so it cannot be selected.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item>Option 1</sl-menu-item>\n  <sl-menu-item disabled>Option 2</sl-menu-item>\n  <sl-menu-item>Option 3</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem>Option 1</SlMenuItem>\n    <SlMenuItem disabled>Option 2</SlMenuItem>\n    <SlMenuItem>Option 3</SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n### Loading\n\nUse the `loading` attribute to indicate that a menu item is busy. Like a disabled menu item, clicks will be suppressed until the loading state is removed.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item>Option 1</sl-menu-item>\n  <sl-menu-item loading>Option 2</sl-menu-item>\n  <sl-menu-item>Option 3</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem>Option 1</SlMenuItem>\n    <SlMenuItem loading>Option 2</SlMenuItem>\n    <SlMenuItem>Option 3</SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n### Checkbox Menu Items\n\nSet the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state.\n\nCheckbox menu items are visually indistinguishable from regular menu items. Their ability to be toggled is primarily inferred from context, much like you'd find in the menu of a native app.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item type=\"checkbox\">Autosave</sl-menu-item>\n  <sl-menu-item type=\"checkbox\" checked>Check Spelling</sl-menu-item>\n  <sl-menu-item type=\"checkbox\">Word Wrap</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem type=\"checkbox\">Autosave</SlMenuItem>\n    <SlMenuItem type=\"checkbox\" checked>\n      Check Spelling\n    </SlMenuItem>\n    <SlMenuItem type=\"checkbox\">Word Wrap</SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n### Value & Selection\n\nThe `value` attribute can be used to assign a hidden value, such as a unique identifier, to a menu item. When an item is selected, the `sl-select` event will be emitted and a reference to the item will be available at `event.detail.item`. You can use this reference to access the selected item's value, its checked state, and more.\n\n```html:preview\n<sl-menu class=\"menu-value\" style=\"max-width: 200px;\">\n  <sl-menu-item value=\"opt-1\">Option 1</sl-menu-item>\n  <sl-menu-item value=\"opt-2\">Option 2</sl-menu-item>\n  <sl-menu-item value=\"opt-3\">Option 3</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item type=\"checkbox\" value=\"opt-4\">Checkbox 4</sl-menu-item>\n  <sl-menu-item type=\"checkbox\" value=\"opt-5\">Checkbox 5</sl-menu-item>\n  <sl-menu-item type=\"checkbox\" value=\"opt-6\">Checkbox 6</sl-menu-item>\n</sl-menu>\n\n<script>\n  const menu = document.querySelector('.menu-value');\n\n  menu.addEventListener('sl-select', event => {\n    const item = event.detail.item;\n\n    // Log value\n    if (item.type === 'checkbox') {\n      console.log(`Selected value: ${item.value} (${item.checked ? 'checked' : 'unchecked'})`);\n    } else {\n      console.log(`Selected value: ${item.value}`);\n    }\n  });\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => {\n  function handleSelect(event) {\n    const item = event.detail.item;\n\n    // Toggle checked state\n    item.checked = !item.checked;\n\n    // Log value\n    console.log(`Selected value: ${item.value}`);\n  }\n\n  return (\n    <SlMenu style={{ maxWidth: '200px' }} onSlSelect={handleSelect}>\n      <SlMenuItem value=\"opt-1\">Option 1</SlMenuItem>\n      <SlMenuItem value=\"opt-2\">Option 2</SlMenuItem>\n      <SlMenuItem value=\"opt-3\">Option 3</SlMenuItem>\n    </SlMenu>\n  );\n};\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/menu-label.md",
    "content": "---\nmeta:\n  title: Menu Label\n  description: Menu labels are used to describe a group of menu items.\nlayout: component\n---\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-label>Fruits</sl-menu-label>\n  <sl-menu-item value=\"apple\">Apple</sl-menu-item>\n  <sl-menu-item value=\"banana\">Banana</sl-menu-item>\n  <sl-menu-item value=\"orange\">Orange</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-label>Vegetables</sl-menu-label>\n  <sl-menu-item value=\"broccoli\">Broccoli</sl-menu-item>\n  <sl-menu-item value=\"carrot\">Carrot</sl-menu-item>\n  <sl-menu-item value=\"zucchini\">Zucchini</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuLabel from '@shoelace-style/shoelace/dist/react/menu-label';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuLabel>Fruits</SlMenuLabel>\n    <SlMenuItem value=\"apple\">Apple</SlMenuItem>\n    <SlMenuItem value=\"banana\">Banana</SlMenuItem>\n    <SlMenuItem value=\"orange\">Orange</SlMenuItem>\n    <SlDivider />\n    <SlMenuLabel>Vegetables</SlMenuLabel>\n    <SlMenuItem value=\"broccoli\">Broccoli</SlMenuItem>\n    <SlMenuItem value=\"carrot\">Carrot</SlMenuItem>\n    <SlMenuItem value=\"zucchini\">Zucchini</SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/menu.md",
    "content": "---\nmeta:\n  title: Menu\n  description: Menus provide a list of options for the user to choose from.\nlayout: component\n---\n\nYou can use [menu items](/components/menu-item), [menu labels](/components/menu-label), and [dividers](/components/divider) to compose a menu. Menus support keyboard interactions, including type-to-select an option.\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item value=\"undo\">Undo</sl-menu-item>\n  <sl-menu-item value=\"redo\">Redo</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n  <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n  <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n  <sl-menu-item value=\"delete\">Delete</sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem value=\"undo\">Undo</SlMenuItem>\n    <SlMenuItem value=\"redo\">Redo</SlMenuItem>\n    <SlDivider />\n    <SlMenuItem value=\"cut\">Cut</SlMenuItem>\n    <SlMenuItem value=\"copy\">Copy</SlMenuItem>\n    <SlMenuItem value=\"paste\">Paste</SlMenuItem>\n    <SlMenuItem value=\"delete\">Delete</SlMenuItem>\n  </SlMenu>\n);\n```\n\n{% endraw %}\n\n:::tip\nMenus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use `<nav>` and `<a>` elements instead.\n:::\n\n## Examples\n\n### In Dropdowns\n\nMenus work really well when used inside [dropdowns](/components/dropdown).\n\n```html:preview\n<sl-dropdown>\n  <sl-button slot=\"trigger\" caret>Edit</sl-button>\n  <sl-menu>\n    <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n    <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n    <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n  </sl-menu>\n</sl-dropdown>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlDropdown>\n    <SlButton slot=\"trigger\" caret>Edit</SlButton>\n    <SlMenu>\n      <SlMenuItem value=\"cut\">Cut</SlMenuItem>\n      <SlMenuItem value=\"copy\">Copy</SlMenuItem>\n      <SlMenuItem value=\"paste\">Paste</SlMenuItem>\n    </SlMenu>\n  </SlDropdown>\n);\n```\n\n### Submenus\n\nTo create a submenu, nest an `<sl-menu slot=\"submenu\">` in any [menu item](/components/menu-item).\n\n```html:preview\n<sl-menu style=\"max-width: 200px;\">\n  <sl-menu-item value=\"undo\">Undo</sl-menu-item>\n  <sl-menu-item value=\"redo\">Redo</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item value=\"cut\">Cut</sl-menu-item>\n  <sl-menu-item value=\"copy\">Copy</sl-menu-item>\n  <sl-menu-item value=\"paste\">Paste</sl-menu-item>\n  <sl-divider></sl-divider>\n  <sl-menu-item>\n    Find\n    <sl-menu slot=\"submenu\">\n      <sl-menu-item value=\"find\">Find…</sl-menu-item>\n      <sl-menu-item value=\"find-previous\">Find Next</sl-menu-item>\n      <sl-menu-item value=\"find-next\">Find Previous</sl-menu-item>\n    </sl-menu>\n  </sl-menu-item>\n  <sl-menu-item>\n    Transformations\n    <sl-menu slot=\"submenu\">\n      <sl-menu-item value=\"uppercase\">Make uppercase</sl-menu-item>\n      <sl-menu-item value=\"lowercase\">Make lowercase</sl-menu-item>\n      <sl-menu-item value=\"capitalize\">Capitalize</sl-menu-item>\n    </sl-menu>\n  </sl-menu-item>\n</sl-menu>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlMenu from '@shoelace-style/shoelace/dist/react/menu';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => (\n  <SlMenu style={{ maxWidth: '200px' }}>\n    <SlMenuItem value=\"undo\">Undo</SlMenuItem>\n    <SlMenuItem value=\"redo\">Redo</SlMenuItem>\n    <SlDivider />\n    <SlMenuItem value=\"cut\">Cut</SlMenuItem>\n    <SlMenuItem value=\"copy\">Copy</SlMenuItem>\n    <SlMenuItem value=\"paste\">Paste</SlMenuItem>\n    <SlDivider />\n    <SlMenuItem>\n      Find\n      <SlMenu slot=\"submenu\">\n        <SlMenuItem value=\"find\">Find…</SlMenuItem>\n        <SlMenuItem value=\"find-previous\">Find Next</SlMenuItem>\n        <SlMenuItem value=\"find-next\">Find Previous</SlMenuItem>\n      </SlMenu>\n    </SlMenuItem>\n    <SlMenuItem>\n      Transformations\n      <SlMenu slot=\"submenu\">\n        <SlMenuItem value=\"uppercase\">Make uppercase</SlMenuItem>\n        <SlMenuItem value=\"lowercase\">Make lowercase</SlMenuItem>\n        <SlMenuItem value=\"capitalize\">Capitalize</SlMenuItem>\n      </SlMenu>\n    </SlMenuItem>\n  </SlMenu>\n);\n```\n\n:::warning\nAs a UX best practice, avoid using more than one level of submenus when possible.\n:::\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/mutation-observer.md",
    "content": "---\nmeta:\n  title: Mutation Observer\n  description: The Mutation Observer component offers a thin, declarative interface to the MutationObserver API.\nlayout: component\n---\n\nThe mutation observer will report changes to the content it wraps through the `sl-mutation` event. When emitted, a collection of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects will be attached to `event.detail` that contains information about how it changed.\n\n```html:preview\n<div class=\"mutation-overview\">\n  <sl-mutation-observer attr=\"variant\">\n    <sl-button variant=\"primary\">Click to mutate</sl-button>\n  </sl-mutation-observer>\n\n  <br />\n  👆 Click the button and watch the console\n\n  <script>\n    const container = document.querySelector('.mutation-overview');\n    const mutationObserver = container.querySelector('sl-mutation-observer');\n    const button = container.querySelector('sl-button');\n    const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];\n    let clicks = 0;\n\n    // Change the button's variant attribute\n    button.addEventListener('click', () => {\n      clicks++;\n      button.setAttribute('variant', variants[clicks % variants.length]);\n    });\n\n    // Log mutations\n    mutationObserver.addEventListener('sl-mutation', event => {\n      console.log(event.detail);\n    });\n  </script>\n\n  <style>\n    .mutation-overview sl-button {\n      margin-bottom: 1rem;\n    }\n  </style>\n</div>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';\n\nconst css = `\n  .resize-observer-overview div {\n    display: flex;\n    border: solid 2px var(--sl-input-border-color);\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    padding: 4rem 2rem;\n  }\n`;\n\nconst variants = ['primary', 'success', 'neutral', 'warning', 'danger'];\nlet clicks = 0;\n\nconst App = () => {\n  const [variant, setVariant] = useState('primary');\n\n  function handleClick() {\n    clicks++;\n    setVariant(variants[clicks % variants.length]);\n  }\n\n  return (\n    <>\n      <SlMutationObserver attr=\"*\" onSlMutation={event => console.log(event.detail)}>\n        <SlButton variant={variant} onClick={handleClick}>\n          Click to mutate\n        </SlButton>\n      </SlMutationObserver>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n:::tip\nWhen you create a mutation observer, you must indicate what changes it should respond to by including at least one of `attr`, `child-list`, or `char-data`. If you don't specify at least one of these attributes, no mutation events will be emitted.\n:::\n\n## Examples\n\n### Child List\n\nUse the `child-list` attribute to watch for new child elements that are added or removed.\n\n```html:preview\n<div class=\"mutation-child-list\">\n  <sl-mutation-observer child-list>\n    <div class=\"buttons\">\n      <sl-button variant=\"primary\">Add button</sl-button>\n    </div>\n  </sl-mutation-observer>\n\n  👆 Add and remove buttons and watch the console\n\n  <script>\n    const container = document.querySelector('.mutation-child-list');\n    const mutationObserver = container.querySelector('sl-mutation-observer');\n    const buttons = container.querySelector('.buttons');\n    const button = container.querySelector('sl-button[variant=\"primary\"]');\n    let i = 0;\n\n    // Add a button\n    button.addEventListener('click', () => {\n      const button = document.createElement('sl-button');\n      button.textContent = ++i;\n      buttons.append(button);\n    });\n\n    // Remove a button\n    buttons.addEventListener('click', event => {\n      const target = event.target.closest('sl-button:not([variant=\"primary\"])');\n      event.stopPropagation();\n\n      if (target) {\n        target.remove();\n      }\n    });\n\n    // Log mutations\n    mutationObserver.addEventListener('sl-mutation', event => {\n      console.log(event.detail);\n    });\n  </script>\n\n  <style>\n    .mutation-child-list .buttons {\n      display: flex;\n      gap: 0.25rem;\n      flex-wrap: wrap;\n      margin-bottom: 1rem;\n    }\n  </style>\n</div>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlMutationObserver from '@shoelace-style/shoelace/dist/react/mutation-observer';\n\nconst css = `\n  .mutation-child-list .buttons {\n    display: flex;\n    gap: .25rem;\n    flex-wrap: wrap;\n    margin-bottom: 1rem;\n  }\n`;\n\nlet buttonCount = 0;\n\nconst App = () => {\n  const [buttonIds, setButtonIds] = useState([]);\n\n  function addButton() {\n    setButtonIds([...buttonIds, ++buttonCount]);\n  }\n\n  function removeButton(id) {\n    setButtonIds(buttonIds.filter(i => i !== id));\n  }\n\n  return (\n    <>\n      <div className=\"mutation-child-list\">\n        <SlMutationObserver child-list onSlMutation={event => console.log(event.detail)}>\n          <div className=\"buttons\">\n            <SlButton variant=\"primary\" onClick={addButton}>\n              Add button\n            </SlButton>\n            {buttonIds.map(id => (\n              <SlButton key={id} variant=\"default\" onClick={() => removeButton(id)}>\n                {id}\n              </SlButton>\n            ))}\n          </div>\n        </SlMutationObserver>\n      </div>\n      👆 Add and remove buttons and watch the console\n      <style>{css}</style>\n    </>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/option.md",
    "content": "---\nmeta:\n  title: Option\n  description: Options define the selectable items within various form controls such as select.\nlayout: component\n---\n\n```html:preview\n<sl-select label=\"Select one\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n## Examples\n\n### Disabled\n\nUse the `disabled` attribute to disable an option and prevent it from being selected.\n\n```html:preview\n<sl-select label=\"Select one\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\" disabled>Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\" disabled>\n      Option 2\n    </SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Prefix & Suffix\n\nAdd icons to the start and end of menu items using the `prefix` and `suffix` slots.\n\n```html:preview\n<sl-select label=\"Select one\">\n  <sl-option value=\"option-1\">\n    <sl-icon slot=\"prefix\" name=\"envelope\"></sl-icon>\n    Email\n    <sl-icon slot=\"suffix\" name=\"patch-check\"></sl-icon>\n  </sl-option>\n\n  <sl-option value=\"option-2\">\n    <sl-icon slot=\"prefix\" name=\"telephone\"></sl-icon>\n    Phone\n    <sl-icon slot=\"suffix\" name=\"patch-check\"></sl-icon>\n  </sl-option>\n\n  <sl-option value=\"option-3\">\n    <sl-icon slot=\"prefix\" name=\"chat-dots\"></sl-icon>\n    Chat\n    <sl-icon slot=\"suffix\" name=\"patch-check\"></sl-icon>\n  </sl-option>\n</sl-select>\n```\n"
  },
  {
    "path": "docs/pages/components/popup.md",
    "content": "---\nmeta:\n  title: Popup\n  description: 'Popup is a utility that lets you declaratively anchor \"popup\" containers to another element.'\nlayout: component\n---\n\nThis component's name is inspired by [`<popup>`](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md). It uses [Floating UI](https://floating-ui.com/) under the hood to provide a well-tested, lightweight, and fully declarative positioning utility for tooltips, dropdowns, and more.\n\nPopup doesn't provide any styles — just positioning! The popup's preferred placement, distance, and skidding (offset) can be configured using attributes. An arrow that points to the anchor can be shown and customized to your liking. Additional positioning options are available and described in more detail below.\n\n:::warning\nPopup is a low-level utility built specifically for positioning elements. Do not mistake it for a [tooltip](/components/tooltip) or similar because _it does not facilitate an accessible experience!_ Almost every correct usage of `<sl-popup>` will involve building other components. It should rarely, if ever, occur directly in your HTML.\n:::\n\n```html:preview\n<div class=\"popup-overview\">\n  <sl-popup placement=\"top\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <div class=\"popup-overview-options\">\n    <sl-select label=\"Placement\" name=\"placement\" value=\"top\" class=\"popup-overview-select\">\n      <sl-option value=\"top\">top</sl-option>\n      <sl-option value=\"top-start\">top-start</sl-option>\n      <sl-option value=\"top-end\">top-end</sl-option>\n      <sl-option value=\"bottom\">bottom</sl-option>\n      <sl-option value=\"bottom-start\">bottom-start</sl-option>\n      <sl-option value=\"bottom-end\">bottom-end</sl-option>\n      <sl-option value=\"right\">right</sl-option>\n      <sl-option value=\"right-start\">right-start</sl-option>\n      <sl-option value=\"right-end\">right-end</sl-option>\n      <sl-option value=\"left\">left</sl-option>\n      <sl-option value=\"left-start\">left-start</sl-option>\n      <sl-option value=\"left-end\">left-end</sl-option>\n    </sl-select>\n    <sl-input type=\"number\" name=\"distance\" label=\"distance\" value=\"0\"></sl-input>\n    <sl-input type=\"number\" name=\"skidding\" label=\"Skidding\" value=\"0\"></sl-input>\n  </div>\n\n  <div class=\"popup-overview-options\">\n    <sl-switch name=\"active\" checked>Active</sl-switch>\n    <sl-switch name=\"arrow\">Arrow</sl-switch>\n  </div>\n</div>\n\n<script>\n  const container = document.querySelector('.popup-overview');\n  const popup = container.querySelector('sl-popup');\n  const select = container.querySelector('sl-select[name=\"placement\"]');\n  const distance = container.querySelector('sl-input[name=\"distance\"]');\n  const skidding = container.querySelector('sl-input[name=\"skidding\"]');\n  const active = container.querySelector('sl-switch[name=\"active\"]');\n  const arrow = container.querySelector('sl-switch[name=\"arrow\"]');\n\n  select.addEventListener('sl-change', () => (popup.placement = select.value));\n  distance.addEventListener('sl-input', () => (popup.distance = distance.value));\n  skidding.addEventListener('sl-input', () => (popup.skidding = skidding.value));\n  active.addEventListener('sl-change', () => (popup.active = active.checked));\n  arrow.addEventListener('sl-change', () => (popup.arrow = arrow.checked));\n</script>\n\n<style>\n  .popup-overview sl-popup {\n    --arrow-color: var(--sl-color-primary-600);\n  }\n\n  .popup-overview span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-overview .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-overview-options {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: end;\n    gap: 1rem;\n  }\n\n  .popup-overview-options sl-select {\n    width: 160px;\n  }\n\n  .popup-overview-options sl-input {\n    width: 100px;\n  }\n\n  .popup-overview-options + .popup-overview-options {\n    margin-top: 1rem;\n  }\n</style>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-overview sl-popup {\n    --arrow-color: var(--sl-color-primary-600);\n  }\n\n  .popup-overview span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-overview .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-overview-options {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: end;\n    gap: 1rem;\n  }\n\n  .popup-overview-options sl-select {\n    width: 160px;\n  }\n\n  .popup-overview-options sl-input {\n    width: 100px;\n  }\n\n  .popup-overview-options + .popup-overview-options {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => {\n  const [placement, setPlacement] = useState('top');\n  const [distance, setDistance] = useState(0);\n  const [skidding, setSkidding] = useState(0);\n  const [active, setActive] = useState(true);\n  const [arrow, setArrow] = useState(false);\n\n  return (\n    <>\n      <div className=\"popup-overview\">\n        <SlPopup\n          placement={placement}\n          active={active || null}\n          distance={distance}\n          skidding={skidding}\n          arrow={arrow || null}\n        >\n          <span slot=\"anchor\" />\n          <div className=\"box\" />\n        </SlPopup>\n\n        <div className=\"popup-overview-options\">\n          <SlSelect\n            label=\"Placement\"\n            name=\"placement\"\n            value={placement}\n            className=\"popup-overview-select\"\n            onSlChange={event => setPlacement(event.target.value)}\n          >\n            <SlMenuItem value=\"top\">top</SlMenuItem>\n            <SlMenuItem value=\"top-start\">top-start</SlMenuItem>\n            <SlMenuItem value=\"top-end\">top-end</SlMenuItem>\n            <SlMenuItem value=\"bottom\">bottom</SlMenuItem>\n            <SlMenuItem value=\"bottom-start\">bottom-start</SlMenuItem>\n            <SlMenuItem value=\"bottom-end\">bottom-end</SlMenuItem>\n            <SlMenuItem value=\"right\">right</SlMenuItem>\n            <SlMenuItem value=\"right-start\">right-start</SlMenuItem>\n            <SlMenuItem value=\"right-end\">right-end</SlMenuItem>\n            <SlMenuItem value=\"left\">left</SlMenuItem>\n            <SlMenuItem value=\"left-start\">left-start</SlMenuItem>\n            <SlMenuItem value=\"left-end\">left-end</SlMenuItem>\n          </SlSelect>\n          <SlInput\n            type=\"number\"\n            name=\"distance\"\n            label=\"distance\"\n            value={distance}\n            onSlInput={event => setDistance(event.target.value)}\n          />\n          <SlInput\n            type=\"number\"\n            name=\"skidding\"\n            label=\"Skidding\"\n            value={skidding}\n            onSlInput={event => setSkidding(event.target.value)}\n          />\n        </div>\n\n        <div className=\"popup-overview-options\">\n          <SlSwitch checked={active} onSlChange={event => setActive(event.target.checked)}>\n            Active\n          </SlSwitch>\n          <SlSwitch checked={arrow} onSlChange={event => setArrow(event.target.checked)}>\n            Arrow\n          </SlSwitch>\n        </div>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n:::tip\nA popup's anchor should not be styled with `display: contents` since the coordinates will not be eligible for calculation. However, if the anchor is a `<slot>` element, popup will use the first assigned element as the anchor. This behavior allows other components to pass anchors through more easily via composition.\n:::\n\n## Examples\n\n### Activating\n\nPopups are inactive and hidden until the `active` attribute is applied. Removing the attribute will tear down all positioning logic and listeners, meaning you can have many idle popups on the page without affecting performance.\n\n```html:preview\n<div class=\"popup-active\">\n  <sl-popup placement=\"top\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <br />\n  <sl-switch checked>Active</sl-switch>\n</div>\n\n<style>\n  .popup-active span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-active .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-active');\n  const popup = container.querySelector('sl-popup');\n  const active = container.querySelector('sl-switch');\n\n  active.addEventListener('sl-change', () => (popup.active = active.checked));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-active span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-active .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => {\n  const [active, setActive] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-active\">\n        <SlPopup placement=\"top\" active={active}>\n          <span slot=\"anchor\" />\n          <div className=\"box\" />\n        </SlPopup>\n\n        <br />\n        <SlSwitch checked={active} onSlChange={event => setActive(event.target.checked)}>\n          Active\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### External Anchors\n\nBy default, anchors are slotted into the popup using the `anchor` slot. If your anchor needs to live outside of the popup, you can pass the anchor's `id` to the `anchor` attribute. Alternatively, you can pass an element reference to the `anchor` property to achieve the same effect without using an `id`.\n\n```html:preview\n<span id=\"external-anchor\"></span>\n\n<sl-popup anchor=\"external-anchor\" placement=\"top\" active>\n  <div class=\"box\"></div>\n</sl-popup>\n\n<style>\n  #external-anchor {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px 0 0 50px;\n  }\n\n  #external-anchor ~ sl-popup .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\n\nconst css = `\n  #external-anchor {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px 0 0 50px;\n  }\n\n  #external-anchor ~ sl-popup .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => {\n  return (\n    <>\n      <span id=\"external-anchor\" />\n\n      <SlPopup anchor=\"external-anchor\" placement=\"top\" active>\n        <div class=\"box\" />\n      </SlPopup>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Placement\n\nUse the `placement` attribute to tell the popup the preferred placement of the popup. Note that the actual position will vary to ensure the panel remains in the viewport if you're using positioning features such as `flip` and `shift`.\n\nSince placement is preferred when using `flip`, you can observe the popup's current placement when it's active by looking at the `data-current-placement` attribute. This attribute will update as the popup flips to find available space and it will be removed when the popup is deactivated.\n\n```html:preview\n<div class=\"popup-placement\">\n  <sl-popup placement=\"top\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <sl-select label=\"Placement\" value=\"top\">\n    <sl-option value=\"top\">top</sl-option>\n    <sl-option value=\"top-start\">top-start</sl-option>\n    <sl-option value=\"top-end\">top-end</sl-option>\n    <sl-option value=\"bottom\">bottom</sl-option>\n    <sl-option value=\"bottom-start\">bottom-start</sl-option>\n    <sl-option value=\"bottom-end\">bottom-end</sl-option>\n    <sl-option value=\"right\">right</sl-option>\n    <sl-option value=\"right-start\">right-start</sl-option>\n    <sl-option value=\"right-end\">right-end</sl-option>\n    <sl-option value=\"left\">left</sl-option>\n    <sl-option value=\"left-start\">left-start</sl-option>\n    <sl-option value=\"left-end\">left-end</sl-option>\n  </sl-select>\n</div>\n\n<style>\n  .popup-placement span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-placement .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-placement sl-select {\n    max-width: 280px;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-placement');\n  const popup = container.querySelector('sl-popup');\n  const select = container.querySelector('sl-select');\n\n  select.addEventListener('sl-change', () => (popup.placement = select.value));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst css = `\n  .popup-placement span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-placement .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-placement sl-select {\n    max-width: 280px;\n  }\n`;\n\nconst App = () => {\n  const [placement, setPlacement] = useState('top');\n\n  return (\n    <div className=\"popup-active\">\n      <div className=\"popup-placement\">\n        <SlPopup placement={placement} active>\n          <span slot=\"anchor\" />\n          <div className=\"box\" />\n        </SlPopup>\n\n        <SlSelect label=\"Placement\" value={placement} onSlChange={event => setPlacement(event.target.value)}>\n          <SlMenuItem value=\"top\">top</SlMenuItem>\n          <SlMenuItem value=\"top-start\">top-start</SlMenuItem>\n          <SlMenuItem value=\"top-end\">top-end</SlMenuItem>\n          <SlMenuItem value=\"bottom\">bottom</SlMenuItem>\n          <SlMenuItem value=\"bottom-start\">bottom-start</SlMenuItem>\n          <SlMenuItem value=\"bottom-end\">bottom-end</SlMenuItem>\n          <SlMenuItem value=\"right\">right</SlMenuItem>\n          <SlMenuItem value=\"right-start\">right-start</SlMenuItem>\n          <SlMenuItem value=\"right-end\">right-end</SlMenuItem>\n          <SlMenuItem value=\"left\">left</SlMenuItem>\n          <SlMenuItem value=\"left-start\">left-start</SlMenuItem>\n          <SlMenuItem value=\"left-end\">left-end</SlMenuItem>\n        </SlSelect>\n      </div>\n\n      <style>{css}</style>\n    </div>\n  );\n};\n```\n\n### Distance\n\nUse the `distance` attribute to change the distance between the popup and its anchor. A positive value will move the popup further away and a negative value will move it closer.\n\n```html:preview\n<div class=\"popup-distance\">\n  <sl-popup placement=\"top\" distance=\"0\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <sl-range min=\"-50\" max=\"50\" step=\"1\" value=\"0\" label=\"Distance\"></sl-range>\n</div>\n\n<style>\n  .popup-distance span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-distance .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-distance sl-range {\n    max-width: 260px;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-distance');\n  const popup = container.querySelector('sl-popup');\n  const distance = container.querySelector('sl-range');\n\n  distance.addEventListener('sl-input', () => (popup.distance = distance.value));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst css = `\n  .popup-distance span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-distance .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-distance sl-range {\n    max-width: 260px;\n  }\n`;\n\nconst App = () => {\n  const [distance, setDistance] = useState(0);\n\n  return (\n    <>\n      <div className=\"popup-distance\">\n        <SlPopup placement=\"top\" distance={distance} active>\n          <span slot=\"anchor\" />\n          <div class=\"box\" />\n        </SlPopup>\n\n        <SlRange\n          label=\"Distance\"\n          min=\"-50\"\n          max=\"50\"\n          step=\"1\"\n          value={distance}\n          onSlChange={event => setDistance(event.target.value)}\n        />\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Skidding\n\nThe `skidding` attribute is similar to `distance`, but instead allows you to offset the popup along the anchor's axis. Both positive and negative values are allowed.\n\n```html:preview\n<div class=\"popup-skidding\">\n  <sl-popup placement=\"top\" skidding=\"0\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <sl-range min=\"-50\" max=\"50\" step=\"1\" value=\"0\" label=\"Skidding\"></sl-range>\n</div>\n\n<style>\n  .popup-skidding span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-skidding .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-skidding sl-range {\n    max-width: 260px;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-skidding');\n  const popup = container.querySelector('sl-popup');\n  const skidding = container.querySelector('sl-range');\n\n  skidding.addEventListener('sl-input', () => (popup.skidding = skidding.value));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst css = `\n  .popup-skidding span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-skidding .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-skidding sl-range {\n    max-width: 260px;\n  }\n`;\n\nconst App = () => {\n  const [skidding, setSkidding] = useState(0);\n\n  return (\n    <>\n      <div className=\"popup-skidding\">\n        <SlPopup placement=\"top\" skidding={skidding} active>\n          <span slot=\"anchor\"></span>\n          <div className=\"box\"></div>\n        </SlPopup>\n\n        <SlRange\n          label=\"Skidding\"\n          min=\"-50\"\n          max=\"50\"\n          step=\"1\"\n          value={skidding}\n          onSlChange={event => setSkidding(event.target.value)}\n        />\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Arrows\n\nAdd an arrow to your popup with the `arrow` attribute. It's usually a good idea to set a `distance` to make room for the arrow. To adjust the arrow's color and size, use the `--arrow-color` and `--arrow-size` custom properties, respectively. You can also target the `arrow` part to add additional styles such as shadows and borders.\n\nBy default, the arrow will be aligned as close to the center of the _anchor_ as possible, considering available space and `arrow-padding`. You can use the `arrow-placement` attribute to force the arrow to align to the start, end, or center of the _popup_ instead.\n\n```html:preview\n<div class=\"popup-arrow\">\n  <sl-popup placement=\"top\" arrow arrow-placement=\"anchor\" distance=\"8\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <div class=\"popup-arrow-options\">\n    <sl-select label=\"Placement\" name=\"placement\" value=\"top\" class=\"popup-overview-select\">\n      <sl-option value=\"top\">top</sl-option>\n      <sl-option value=\"top-start\">top-start</sl-option>\n      <sl-option value=\"top-end\">top-end</sl-option>\n      <sl-option value=\"bottom\">bottom</sl-option>\n      <sl-option value=\"bottom-start\">bottom-start</sl-option>\n      <sl-option value=\"bottom-end\">bottom-end</sl-option>\n      <sl-option value=\"right\">right</sl-option>\n      <sl-option value=\"right-start\">right-start</sl-option>\n      <sl-option value=\"right-end\">right-end</sl-option>\n      <sl-option value=\"left\">left</sl-option>\n      <sl-option value=\"left-start\">left-start</sl-option>\n      <sl-option value=\"left-end\">left-end</sl-option>\n    </sl-select>\n\n    <sl-select label=\"Arrow Placement\" name=\"arrow-placement\" value=\"anchor\">\n      <sl-option value=\"anchor\">anchor</sl-option>\n      <sl-option value=\"start\">start</sl-option>\n      <sl-option value=\"end\">end</sl-option>\n      <sl-option value=\"center\">center</sl-option>\n    </sl-select>\n  </div>\n\n  <div class=\"popup-arrow-options\">\n    <sl-switch name=\"arrow\" checked>Arrow</sl-switch>\n  </div>\n\n  <style>\n    .popup-arrow sl-popup {\n      --arrow-color: var(--sl-color-primary-600);\n    }\n\n    .popup-arrow span[slot='anchor'] {\n      display: inline-block;\n      width: 150px;\n      height: 150px;\n      border: dashed 2px var(--sl-color-neutral-600);\n      margin: 50px;\n    }\n\n    .popup-arrow .box {\n      width: 100px;\n      height: 50px;\n      background: var(--sl-color-primary-600);\n      border-radius: var(--sl-border-radius-medium);\n    }\n\n    .popup-arrow-options {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: end;\n      gap: 1rem;\n    }\n\n    .popup-arrow-options sl-select {\n      width: 160px;\n    }\n\n    .popup-arrow-options + .popup-arrow-options {\n      margin-top: 1rem;\n    }\n  </style>\n\n  <script>\n    const container = document.querySelector('.popup-arrow');\n    const popup = container.querySelector('sl-popup');\n    const placement = container.querySelector('[name=\"placement\"]');\n    const arrowPlacement = container.querySelector('[name=\"arrow-placement\"]');\n    const arrow = container.querySelector('[name=\"arrow\"]');\n\n    placement.addEventListener('sl-change', () => (popup.placement = placement.value));\n    arrowPlacement.addEventListener('sl-change', () => (popup.arrowPlacement = arrowPlacement.value));\n    arrow.addEventListener('sl-change', () => (popup.arrow = arrow.checked));\n  </script>\n</div>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-arrow sl-popup {\n    --arrow-color: var(--sl-color-primary-600);\n  }\n\n  .popup-arrow span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-arrow .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-arrow-options {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: end;\n    gap: 1rem;\n  }\n\n  .popup-arrow-options sl-select {\n    width: 160px;\n  }\n\n  .popup-arrow-options + .popup-arrow-options {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => {\n  const [placement, setPlacement] = useState('top');\n  const [arrowPlacement, setArrowPlacement] = useState('anchor');\n  const [arrow, setArrow] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-arrow\">\n        <SlPopup placement={placement} arrow={arrow || null} arrow-placement={arrowPlacement} distance=\"8\" active>\n          <span slot=\"anchor\" />\n          <div className=\"box\" />\n        </SlPopup>\n\n        <div className=\"popup-arrow-options\">\n          <SlSelect\n            label=\"Placement\"\n            name=\"placement\"\n            value={placement}\n            className=\"popup-overview-select\"\n            onSlChange={event => setPlacement(event.target.value)}\n          >\n            <SlMenuItem value=\"top\">top</SlMenuItem>\n            <SlMenuItem value=\"top-start\">top-start</SlMenuItem>\n            <SlMenuItem value=\"top-end\">top-end</SlMenuItem>\n            <SlMenuItem value=\"bottom\">bottom</SlMenuItem>\n            <SlMenuItem value=\"bottom-start\">bottom-start</SlMenuItem>\n            <SlMenuItem value=\"bottom-end\">bottom-end</SlMenuItem>\n            <SlMenuItem value=\"right\">right</SlMenuItem>\n            <SlMenuItem value=\"right-start\">right-start</SlMenuItem>\n            <SlMenuItem value=\"right-end\">right-end</SlMenuItem>\n            <SlMenuItem value=\"left\">left</SlMenuItem>\n            <SlMenuItem value=\"left-start\">left-start</SlMenuItem>\n            <SlMenuItem value=\"left-end\">left-end</SlMenuItem>\n          </SlSelect>\n\n          <SlSelect\n            label=\"Arrow Placement\"\n            name=\"arrow-placement\"\n            value={arrowPlacement}\n            onSlChange={event => setArrowPlacement(event.target.value)}\n          >\n            <SlMenuItem value=\"anchor\">anchor</SlMenuItem>\n            <SlMenuItem value=\"start\">start</SlMenuItem>\n            <SlMenuItem value=\"end\">end</SlMenuItem>\n            <SlMenuItem value=\"center\">center</SlMenuItem>\n          </SlSelect>\n        </div>\n\n        <div className=\"popup-arrow-options\">\n          <SlSwitch name=\"arrow\" checked={arrow} onSlChange={event => setArrow(event.target.checked)}>\n            Arrow\n          </SlSwitch>\n        </div>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Syncing with the Anchor's Dimensions\n\nUse the `sync` attribute to make the popup the same width or height as the anchor element. This is useful for controls that need the popup to stay the same width or height as the trigger.\n\n```html:preview\n<div class=\"popup-sync\">\n  <sl-popup placement=\"top\" sync=\"width\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <sl-select value=\"width\" label=\"Sync\">\n    <sl-option value=\"width\">Width</sl-option>\n    <sl-option value=\"height\">Height</sl-option>\n    <sl-option value=\"both\">Both</sl-option>\n    <sl-option value=\"\">None</sl-option>\n  </sl-select>\n</div>\n\n<style>\n  .popup-sync span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-sync .box {\n    width: 100%;\n    height: 100%;\n    min-width: 50px;\n    min-height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-sync sl-select {\n    width: 160px;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-sync');\n  const popup = container.querySelector('sl-popup');\n  const fixed = container.querySelector('sl-switch');\n  const sync = container.querySelector('sl-select');\n\n  sync.addEventListener('sl-change', () => (popup.sync = sync.value));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst css = `\n  .popup-sync span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-sync .box {\n    width: 100%;\n    height: 100%;\n    min-width: 50px;\n    min-height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-sync sl-switch {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => {\n  const [sync, setSync] = useState('width');\n\n  return (\n    <>\n      <div class=\"popup-sync\">\n        <SlPopup placement=\"top\" sync={sync} active>\n          <span slot=\"anchor\" />\n          <div class=\"box\" />\n        </SlPopup>\n\n        <SlSelect value={sync} label=\"Sync\" onSlChange={event => setSync(event.target.value)}>\n          <SlMenuItem value=\"width\">Width</SlMenuItem>\n          <SlMenuItem value=\"height\">Height</SlMenuItem>\n          <SlMenuItem value=\"both\">Both</SlMenuItem>\n          <SlMenuItem value=\"\">None</SlMenuItem>\n        </SlSelect>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Positioning Strategy\n\nBy default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has `overflow: auto|hidden`, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the `strategy` attribute to `fixed`.\n\nThe fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.\n\nIn this example, you can see how the popup breaks out of the overflow container when it's fixed. The fixed positioning strategy tends to be less performant than absolute, so avoid using it unnecessarily.\n\nToggle the switch and scroll the container to see the difference.\n\n```html:preview\n<div class=\"popup-strategy\">\n  <div class=\"overflow\">\n    <sl-popup placement=\"top\" strategy=\"fixed\" active>\n      <span slot=\"anchor\"></span>\n      <div class=\"box\"></div>\n    </sl-popup>\n  </div>\n\n  <sl-switch checked>Fixed</sl-switch>\n</div>\n\n<style>\n  .popup-strategy .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-strategy span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 150px 50px;\n  }\n\n  .popup-strategy .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-strategy sl-switch {\n    margin-top: 1rem;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-strategy');\n  const popup = container.querySelector('sl-popup');\n  const fixed = container.querySelector('sl-switch');\n\n  fixed.addEventListener('sl-change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-strategy .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-strategy span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 150px 50px;\n  }\n\n  .popup-strategy .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-strategy sl-switch {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => {\n  const [fixed, setFixed] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-strategy\">\n        <div className=\"overflow\">\n          <SlPopup placement=\"top\" strategy={fixed ? 'fixed' : 'absolute'} active>\n            <span slot=\"anchor\" />\n            <div className=\"box\" />\n          </SlPopup>\n        </div>\n\n        <SlSwitch checked={fixed} onSlChange={event => setFixed(event.target.checked)}>\n          Fixed\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Flip\n\nWhen the popup doesn't have enough room in its preferred placement, it can automatically flip to keep it in view. To enable this, use the `flip` attribute. By default, the popup will flip to the opposite placement, but you can configure preferred fallback placements using `flip-fallback-placement` and `flip-fallback-strategy`. Additional options are available to control the flip behavior's boundary and padding.\n\nScroll the container to see how the popup flips to prevent clipping.\n\n```html:preview\n<div class=\"popup-flip\">\n  <div class=\"overflow\">\n    <sl-popup placement=\"top\" flip active>\n      <span slot=\"anchor\"></span>\n      <div class=\"box\"></div>\n    </sl-popup>\n  </div>\n\n  <br />\n  <sl-switch checked>Flip</sl-switch>\n</div>\n\n<style>\n  .popup-flip .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-flip span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 150px 50px;\n  }\n\n  .popup-flip .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-flip');\n  const popup = container.querySelector('sl-popup');\n  const flip = container.querySelector('sl-switch');\n\n  flip.addEventListener('sl-change', () => (popup.flip = flip.checked));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-flip .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-flip span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 150px 50px;\n  }\n\n  .popup-flip .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => {\n  const [flip, setFlip] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-flip\">\n        <div className=\"overflow\">\n          <SlPopup placement=\"top\" flip={flip} active>\n            <span slot=\"anchor\" />\n            <div className=\"box\" />\n          </SlPopup>\n        </div>\n\n        <br />\n        <SlSwitch checked={flip} onSlChange={event => setFlip(event.target.checked)}>\n          Flip\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Flip Fallbacks\n\nWhile using the `flip` attribute, you can customize the placement of the popup when the preferred placement doesn't have room. For this, use `flip-fallback-placements` and `flip-fallback-strategy`.\n\nIf the preferred placement doesn't have room, the first suitable placement found in `flip-fallback-placement` will be used. The value of this attribute must be a string including any number of placements separated by a space, e.g. `\"right bottom\"`.\n\nIf no fallback placement works, the final placement will be determined by `flip-fallback-strategy`. This value can be either `initial` (default), where the placement reverts to the position in `placement`, or `best-fit`, where the placement is chosen based on available space.\n\nScroll the container to see how the popup changes it's fallback placement to prevent clipping.\n\n```html:preview\n<div class=\"popup-flip-fallbacks\">\n  <div class=\"overflow\">\n    <sl-popup placement=\"top\" flip flip-fallback-placements=\"right bottom\" flip-fallback-strategy=\"initial\" active>\n      <span slot=\"anchor\"></span>\n      <div class=\"box\"></div>\n    </sl-popup>\n  </div>\n</div>\n\n<style>\n  .popup-flip-fallbacks .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-flip-fallbacks span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 250px 50px;\n  }\n\n  .popup-flip-fallbacks .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\n\nconst css = `\n  .popup-flip-fallbacks .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-flip-fallbacks span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 250px 50px;\n  }\n\n  .popup-flip-fallbacks .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => {\n  return (\n    <>\n      <div className=\"popup-flip-fallbacks\">\n        <div className=\"overflow\">\n          <SlPopup placement=\"top\" flip flip-fallback-placements=\"right bottom\" flip-fallback-strategy=\"initial\" active>\n            <span slot=\"anchor\" />\n            <div className=\"box\" />\n          </SlPopup>\n        </div>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Shift\n\nWhen a popup is longer than its anchor, it risks being clipped by an overflowing container. In this case, use the `shift` attribute to shift the popup along its axis and back into view. You can customize the shift behavior using `shiftBoundary` and `shift-padding`.\n\nToggle the switch to see the difference.\n\n```html:preview\n<div class=\"popup-shift\">\n  <div class=\"overflow\">\n    <sl-popup placement=\"top\" shift shift-padding=\"10\" active>\n      <span slot=\"anchor\"></span>\n      <div class=\"box\"></div>\n    </sl-popup>\n  </div>\n\n  <sl-switch checked>Shift</sl-switch>\n</div>\n\n<style>\n  .popup-shift .overflow {\n    position: relative;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-shift span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 60px 0 0 10px;\n  }\n\n  .popup-shift .box {\n    width: 300px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-shift');\n  const popup = container.querySelector('sl-popup');\n  const shift = container.querySelector('sl-switch');\n\n  shift.addEventListener('sl-change', () => (popup.shift = shift.checked));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-shift .overflow {\n    position: relative;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-shift span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 60px 0 0 10px;\n  }\n\n  .popup-shift .box {\n    width: 300px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => {\n  const [shift, setShift] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-shift\">\n        <div className=\"overflow\">\n          <SlPopup placement=\"top\" shift={shift} shift-padding=\"10\" active>\n            <span slot=\"anchor\" />\n            <div className=\"box\" />\n          </SlPopup>\n        </div>\n\n        <SlSwitch checked={shift} onSlChange={event => setShift(event.target.checked)}>\n          Shift\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Auto-size\n\nUse the `auto-size` attribute to tell the popup to resize when necessary to prevent it from getting clipped. Possible values are `horizontal`, `vertical`, and `both`. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.\n\nWhen using `auto-size`, one or both of `--auto-size-available-width` and `--auto-size-available-height` will be applied to the host element. These values determine the available space the popover has before clipping will occur. Since they cascade, you can use them to set a max-width/height on your popup's content and easily control its overflow.\n\nScroll the container to see the popup resize as its available space changes.\n\n```html:preview\n<div class=\"popup-auto-size\">\n  <div class=\"overflow\">\n    <sl-popup placement=\"top\" auto-size=\"both\" auto-size-padding=\"10\" active>\n      <span slot=\"anchor\"></span>\n      <div class=\"box\"></div>\n    </sl-popup>\n  </div>\n\n  <br />\n  <sl-switch checked>Auto-size</sl-switch>\n</div>\n\n<style>\n  .popup-auto-size .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-auto-size span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 250px 50px 100px 50px;\n  }\n\n  .popup-auto-size .box {\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n\n    /* This sets the preferred size of the popup's content */\n    width: 100px;\n    height: 200px;\n\n    /* This sets the maximum dimensions and allows scrolling when auto-size kicks in */\n    max-width: var(--auto-size-available-width);\n    max-height: var(--auto-size-available-height);\n    overflow: auto;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-auto-size');\n  const popup = container.querySelector('sl-popup');\n  const autoSize = container.querySelector('sl-switch');\n\n  autoSize.addEventListener('sl-change', () => (popup.autoSize = autoSize.checked ? 'both' : ''));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-auto-size .overflow {\n    position: relative;\n    height: 300px;\n    border: solid 2px var(--sl-color-neutral-200);\n    overflow: auto;\n  }\n\n  .popup-auto-size span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 250px 50px 100px 50px;\n  }\n\n  .popup-auto-size .box {\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n\n    /* This sets the preferred size of the popup's content */\n    width: 100px;\n    height: 200px;\n\n    /* This sets the maximum dimensions and allows scrolling when auto-size kicks in */\n    max-width: var(--auto-size-available-width);\n    max-height: var(--auto-size-available-height);\n    overflow: auto;\n  }\n`;\n\nconst App = () => {\n  const [autoSize, setAutoSize] = useState(true);\n\n  return (\n    <>\n      <div className=\"popup-auto-size\">\n        <div className=\"overflow\">\n          <SlPopup placement=\"top\" auto-size={autoSize ? 'both' || null} auto-size-padding=\"10\" active>\n            <span slot=\"anchor\" />\n            <div className=\"box\" />\n          </SlPopup>\n        </div>\n\n        <br />\n        <SlSwitch checked={autoSize} onSlChange={event => setAutoSize(event.target.checked)}>\n          Auto-size\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Hover Bridge\n\nWhen a gap exists between the anchor and the popup element, this option will add a \"hover bridge\" that fills the gap using an invisible element. This makes listening for events such as `mouseover` and `mouseout` more sane because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is active. For demonstration purposes, the bridge in this example is shown in orange.\n\n```html:preview\n<div class=\"popup-hover-bridge\">\n  <sl-popup placement=\"top\" hover-bridge distance=\"10\" skidding=\"0\" active>\n    <span slot=\"anchor\"></span>\n    <div class=\"box\"></div>\n  </sl-popup>\n\n  <br>\n  <sl-switch checked>Hover Bridge</sl-switch><br>\n  <sl-range min=\"0\" max=\"50\" step=\"1\" value=\"10\" label=\"Distance\"></sl-range>\n  <sl-range min=\"-50\" max=\"50\" step=\"1\" value=\"0\" label=\"Skidding\"></sl-range>\n</div>\n\n<style>\n  .popup-hover-bridge span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-hover-bridge .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-hover-bridge sl-range {\n    max-width: 260px;\n    margin-top: .5rem;\n  }\n\n  .popup-hover-bridge sl-popup::part(hover-bridge) {\n    background: tomato;\n    opacity: .5;\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.popup-hover-bridge');\n  const popup = container.querySelector('sl-popup');\n  const hoverBridge = container.querySelector('sl-switch');\n  const distance = container.querySelector('sl-range[label=\"Distance\"]');\n  const skidding = container.querySelector('sl-range[label=\"Skidding\"]');\n\n  distance.addEventListener('sl-input', () => (popup.distance = distance.value));\n  skidding.addEventListener('sl-input', () => (popup.skidding = skidding.value));\n  hoverBridge.addEventListener('sl-change', () => (popup.hoverBridge = hoverBridge.checked));\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  .popup-hover-bridge span[slot='anchor'] {\n    display: inline-block;\n    width: 150px;\n    height: 150px;\n    border: dashed 2px var(--sl-color-neutral-600);\n    margin: 50px;\n  }\n\n  .popup-hover-bridge .box {\n    width: 100px;\n    height: 50px;\n    background: var(--sl-color-primary-600);\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .popup-hover-bridge sl-range {\n    max-width: 260px;\n    margin-top: .5rem;\n  }\n\n  .popup-hover-bridge sl-popup::part(hover-bridge) {\n    background: tomato;\n    opacity: .5;\n  }\n`;\n\nconst App = () => {\n  const [hoverBridge, setHoverBridge] = useState(true);\n  const [distance, setDistance] = useState(10);\n  const [skidding, setSkidding] = useState(0);\n\n  return (\n    <>\n      <div class=\"popup-hover-bridge\">\n        <SlPopup placement=\"top\" hover-bridge={hoverBridge} distance={distance} skidding={skidding} active>\n          <span slot=\"anchor\" />\n          <div class=\"box\" />\n        </SlPopup>\n\n        <br />\n        <SlSwitch\n          checked={hoverBridge}\n          onSlChange={event => setHoverBridge(event.target.checked)}\n         >\n          Hover Bridge\n        </SlSwitch><br />\n        <SlRange\n          min=\"0\"\n          max=\"50\"\n          step=\"1\"\n          value={distance}\n          label=\"Distance\"\n          onSlInput={event => setDistance(event.target.value)}\n        />\n        <SlRange\n          min=\"-50\"\n          max=\"50\"\n          step=\"1\"\n          value={skidding}\n          label=\"Skidding\"\n          onSlInput={event => setSkidding(event.target.value)}\n        />\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Virtual Elements\n\nIn most cases, popups are anchored to an actual element. Sometimes, it can be useful to anchor them to a non-element. To do this, you can pass a `VirtualElement` to the anchor property. A virtual element must contain a function called `getBoundingClientRect()` that returns a [`DOMRect`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect) object as shown below.\n\n```ts\nconst virtualElement = {\n  getBoundingClientRect() {\n    // ...\n    return { width, height, x, y, top, left, right, bottom };\n  }\n};\n```\n\nThis example anchors a popup to the mouse cursor using a virtual element. As such, a mouse is required to properly view it.\n\n```html:preview\n<div class=\"popup-virtual-element\">\n  <sl-popup placement=\"right-start\">\n    <div class=\"circle\"></div>\n  </sl-popup>\n\n  <sl-switch>Highlight mouse cursor</sl-switch>\n</div>\n\n<script>\n  const container = document.querySelector('.popup-virtual-element');\n  const popup = container.querySelector('sl-popup');\n  const circle = container.querySelector('.circle');\n  const enabled = container.querySelector('sl-switch');\n  let clientX = 0;\n  let clientY = 0;\n\n  // Set the virtual element as a property\n  popup.anchor = {\n    getBoundingClientRect() {\n      return {\n        width: 0,\n        height: 0,\n        x: clientX,\n        y: clientY,\n        top: clientY,\n        left: clientX,\n        right: clientX,\n        bottom: clientY\n      };\n    }\n  };\n\n  // Only activate the popup when the switch is checked\n  enabled.addEventListener('sl-change', () => {\n    popup.active = enabled.checked;\n  });\n\n  // Listen for the mouse to move\n  document.addEventListener('mousemove', handleMouseMove);\n\n  // Update the virtual element as the mouse moves\n  function handleMouseMove(event) {\n    clientX = event.clientX;\n    clientY = event.clientY;\n\n    // Reposition the popup when the virtual anchor moves\n    if (popup.active) {\n      popup.reposition();\n    }\n  }\n</script>\n\n<style>\n  /* If you need to set a z-index, set it on the popup part like this */\n  .popup-virtual-element sl-popup::part(popup) {\n    z-index: 1000;\n    pointer-events: none;\n  }\n\n  .popup-virtual-element .circle {\n    width: 100px;\n    height: 100px;\n    border: solid 4px var(--sl-color-primary-600);\n    border-radius: 50%;\n    translate: -50px -50px;\n    animation: 1s virtual-cursor infinite;\n  }\n\n  @keyframes virtual-cursor {\n    0% { scale: 1; }\n    50% { scale: 1.1; }\n  }\n</style>\n```\n\n```jsx:react\nimport { useRef, useState } from 'react';\nimport SlPopup from '@shoelace-style/shoelace/dist/react/popup';\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst css = `\n  /* If you need to set a z-index, set it on the popup part like this */\n  .popup-virtual-element sl-popup::part(popup) {\n    z-index: 1000;\n    pointer-events: none;\n  }\n\n  .popup-virtual-element .circle {\n    width: 100px;\n    height: 100px;\n    border: solid 4px var(--sl-color-primary-600);\n    border-radius: 50%;\n    translate: -50px -50px;\n    animation: 1s virtual-cursor infinite;\n  }\n\n  @keyframes virtual-cursor {\n    0% { scale: 1; }\n    50% { scale: 1.1; }\n  }\n`;\n\nconst App = () => {\n  const [enabled, setEnabled] = useState(false);\n  const [clientX, setClientX] = useState(0);\n  const [clientY, setClientY] = useState(0);\n  const popup = useRef(null);\n  const circle = useRef(null);\n  const virtualElement = {\n    getBoundingClientRect() {\n      return {\n        width: 0,\n        height: 0,\n        x: clientX,\n        y: clientY,\n        top: clientY,\n        left: clientX,\n        right: clientX,\n        bottom: clientY\n      };\n    }\n  };\n\n  // Listen for the mouse to move\n  document.addEventListener('mousemove', handleMouseMove);\n\n  // Update the virtual element as the mouse moves\n  function handleMouseMove(event) {\n    setClientX(event.clientX);\n    setClientY(event.clientY);\n\n    // Reposition the popup when the virtual anchor moves\n    if (popup.active) {\n      popup.current.reposition();\n    }\n  }\n\n  return (\n    <>\n      <div className=\"popup-virtual-element\">\n        <SlPopup\n          ref={popup}\n          placement=\"right-start\"\n          active={enabled}\n          anchor={virtualElement}\n        >\n          <div ref={circle} className=\"circle\" />\n        </SlPopup>\n\n        <SlSwitch checked={enabled} onSlChange={event => setEnabled(event.target.checked)}>\n          Highlight mouse cursor\n        </SlSwitch>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\nSometimes the `getBoundingClientRects` might be derived from a real element. In this case provide the anchor element as context to ensure clipping and position updates for the popup work well.\n\n```ts\nconst virtualElement = {\n  getBoundingClientRect() {\n    // ...\n    return { width, height, x, y, top, left, right, bottom };\n  },\n  contextElement: anchorElement\n};\n```\n"
  },
  {
    "path": "docs/pages/components/progress-bar.md",
    "content": "---\nmeta:\n  title: Progress Bar\n  description: Progress bars are used to show the status of an ongoing operation.\nlayout: component\n---\n\n```html:preview\n<sl-progress-bar value=\"50\"></sl-progress-bar>\n```\n\n```jsx:react\nimport SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';\n\nconst App = () => <SlProgressBar value={50} />;\n```\n\n## Examples\n\n### Labels\n\nUse the `label` attribute to label the progress bar and tell assistive devices how to announce it.\n\n```html:preview\n<sl-progress-bar value=\"50\" label=\"Upload progress\"></sl-progress-bar>\n```\n\n```jsx:react\nimport SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';\n\nconst App = () => <SlProgressBar value=\"50\" label=\"Upload progress\" />;\n```\n\n### Custom Height\n\nUse the `--height` custom property to set the progress bar's height.\n\n```html:preview\n<sl-progress-bar value=\"50\" style=\"--height: 6px;\"></sl-progress-bar>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';\n\nconst App = () => <SlProgressBar value={50} style={{ '--height': '6px' }} />;\n```\n\n{% endraw %}\n\n### Showing Values\n\nUse the default slot to show a value.\n\n```html:preview\n<sl-progress-bar value=\"50\" class=\"progress-bar-values\">50%</sl-progress-bar>\n\n<br />\n\n<sl-button circle><sl-icon name=\"dash\" label=\"Decrease\"></sl-icon></sl-button>\n<sl-button circle><sl-icon name=\"plus\" label=\"Increase\"></sl-icon></sl-button>\n\n<script>\n  const progressBar = document.querySelector('.progress-bar-values');\n  const subtractButton = progressBar.nextElementSibling.nextElementSibling;\n  const addButton = subtractButton.nextElementSibling;\n\n  addButton.addEventListener('click', () => {\n    const value = Math.min(100, progressBar.value + 10);\n    progressBar.value = value;\n    progressBar.textContent = `${value}%`;\n  });\n\n  subtractButton.addEventListener('click', () => {\n    const value = Math.max(0, progressBar.value - 10);\n    progressBar.value = value;\n    progressBar.textContent = `${value}%`;\n  });\n</script>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';\n\nconst App = () => {\n  const [value, setValue] = useState(50);\n\n  function adjustValue(amount) {\n    let newValue = value + amount;\n    if (newValue < 0) newValue = 0;\n    if (newValue > 100) newValue = 100;\n    setValue(newValue);\n  }\n\n  return (\n    <>\n      <SlProgressBar value={value}>{value}%</SlProgressBar>\n\n      <br />\n\n      <SlButton circle onClick={() => adjustValue(-10)}>\n        <SlIcon name=\"dash\" label=\"Decrease\" />\n      </SlButton>\n\n      <SlButton circle onClick={() => adjustValue(10)}>\n        <SlIcon name=\"plus\" label=\"Increase\" />\n      </SlButton>\n    </>\n  );\n};\n```\n\n### Indeterminate\n\nThe `indeterminate` attribute can be used to inform the user that the operation is pending, but its status cannot currently be determined. In this state, `value` is ignored and the label, if present, will not be shown.\n\n```html:preview\n<sl-progress-bar indeterminate></sl-progress-bar>\n```\n\n```jsx:react\nimport SlProgressBar from '@shoelace-style/shoelace/dist/react/progress-bar';\n\nconst App = () => <SlProgressBar indeterminate />;\n```\n"
  },
  {
    "path": "docs/pages/components/progress-ring.md",
    "content": "---\nmeta:\n  title: Progress Ring\n  description: Progress rings are used to show the progress of a determinate operation in a circular fashion.\nlayout: component\n---\n\n```html:preview\n<sl-progress-ring value=\"25\"></sl-progress-ring>\n```\n\n```jsx:react\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => <SlProgressRing value=\"25\" />;\n```\n\n## Examples\n\n### Size\n\nUse the `--size` custom property to set the diameter of the progress ring.\n\n```html:preview\n<sl-progress-ring value=\"50\" style=\"--size: 200px;\"></sl-progress-ring>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => <SlProgressRing value=\"50\" style={{ '--size': '200px' }} />;\n```\n\n{% endraw %}\n\n### Track and Indicator Width\n\nUse the `--track-width` and `--indicator-width` custom properties to set the width of the progress ring's track and indicator.\n\n```html:preview\n<sl-progress-ring value=\"50\" style=\"--track-width: 6px; --indicator-width: 12px;\"></sl-progress-ring>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => <SlProgressRing value=\"50\" style={{ '--track-width': '6px', '--indicator-width': '12px' }} />;\n```\n\n{% endraw %}\n\n### Colors\n\nTo change the color, use the `--track-color` and `--indicator-color` custom properties.\n\n```html:preview\n<sl-progress-ring\n  value=\"50\"\n  style=\"\n    --track-color: pink;\n    --indicator-color: deeppink;\n  \"\n></sl-progress-ring>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => (\n  <SlProgressRing\n    value=\"50\"\n    style={{\n      '--track-color': 'pink',\n      '--indicator-color': 'deeppink'\n    }}\n  />\n);\n```\n\n{% endraw %}\n\n### Labels\n\nUse the `label` attribute to label the progress ring and tell assistive devices how to announce it.\n\n```html:preview\n<sl-progress-ring value=\"50\" label=\"Upload progress\"></sl-progress-ring>\n```\n\n```jsx:react\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => <SlProgressRing value=\"50\" label=\"Upload progress\" />;\n```\n\n### Showing Values\n\nUse the default slot to show a label inside the progress ring.\n\n```html:preview\n<sl-progress-ring value=\"50\" class=\"progress-ring-values\" style=\"margin-bottom: .5rem;\">50%</sl-progress-ring>\n\n<br />\n\n<sl-button circle><sl-icon name=\"dash\" label=\"Decrease\"></sl-icon></sl-button>\n<sl-button circle><sl-icon name=\"plus\" label=\"Increase\"></sl-icon></sl-button>\n\n<script>\n  const progressRing = document.querySelector('.progress-ring-values');\n  const subtractButton = progressRing.nextElementSibling.nextElementSibling;\n  const addButton = subtractButton.nextElementSibling;\n\n  addButton.addEventListener('click', () => {\n    const value = Math.min(100, progressRing.value + 10);\n    progressRing.value = value;\n    progressRing.textContent = `${value}%`;\n  });\n\n  subtractButton.addEventListener('click', () => {\n    const value = Math.max(0, progressRing.value - 10);\n    progressRing.value = value;\n    progressRing.textContent = `${value}%`;\n  });\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlProgressRing from '@shoelace-style/shoelace/dist/react/progress-ring';\n\nconst App = () => {\n  const [value, setValue] = useState(50);\n\n  function adjustValue(amount) {\n    let newValue = value + amount;\n    if (newValue < 0) newValue = 0;\n    if (newValue > 100) newValue = 100;\n    setValue(newValue);\n  }\n\n  return (\n    <>\n      <SlProgressRing value={value} style={{ marginBottom: '.5rem' }}>\n        {value}%\n      </SlProgressRing>\n\n      <br />\n\n      <SlButton circle onClick={() => adjustValue(-10)}>\n        <SlIcon name=\"dash\" label=\"Decrease\" />\n      </SlButton>\n\n      <SlButton circle onClick={() => adjustValue(10)}>\n        <SlIcon name=\"plus\" label=\"Increase\" />\n      </SlButton>\n    </>\n  );\n};\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/qr-code.md",
    "content": "---\nmeta:\n  title: QR Code\n  description: Generates a QR code and renders it using the Canvas API.\nlayout: component\n---\n\nQR codes are useful for providing small pieces of information to users who can quickly scan them with a smartphone. Most smartphones have built-in QR code scanners, so simply pointing the camera at a QR code will decode it and allow the user to visit a website, dial a phone number, read a message, etc.\n\n```html:preview\n<div class=\"qr-overview\">\n  <sl-qr-code value=\"https://shoelace.style/\" label=\"Scan this code to visit Shoelace on the web!\"></sl-qr-code>\n  <br />\n\n  <sl-input maxlength=\"255\" clearable label=\"Value\"></sl-input>\n</div>\n\n<script>\n  const container = document.querySelector('.qr-overview');\n  const qrCode = container.querySelector('sl-qr-code');\n  const input = container.querySelector('sl-input');\n\n  customElements.whenDefined('sl-qr-code').then(() => {\n    input.value = qrCode.value;\n    input.addEventListener('sl-input', () => (qrCode.value = input.value));\n  });\n</script>\n\n<style>\n  .qr-overview {\n    max-width: 256px;\n  }\n\n  .qr-overview sl-input {\n    margin-top: 1rem;\n  }\n</style>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst css = `\n  .qr-overview {\n    max-width: 256px;\n  }\n\n  .qr-overview sl-input {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => {\n  const [value, setValue] = useState('https://shoelace.style/');\n\n  return (\n    <>\n      <div className=\"qr-overview\">\n        <SlQrCode value={value} label=\"Scan this code to visit Shoelace on the web!\" />\n        <br />\n\n        <SlInput maxlength=\"255\" clearable onInput={event => setValue(event.target.value)} />\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n## Examples\n\n### Colors\n\nUse the `fill` and `background` attributes to modify the QR code's colors. You should always ensure good contrast for optimal compatibility with QR code scanners.\n\n```html:preview\n<sl-qr-code value=\"https://shoelace.style/\" fill=\"deeppink\" background=\"white\"></sl-qr-code>\n```\n\n```jsx:react\nimport SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';\n\nconst App = () => <SlQrCode value=\"https://shoelace.style/\" fill=\"deeppink\" background=\"white\" />;\n```\n\n### Size\n\nUse the `size` attribute to change the size of the QR code.\n\n```html:preview\n<sl-qr-code value=\"https://shoelace.style/\" size=\"64\"></sl-qr-code>\n```\n\n```jsx:react\nimport SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';\n\nconst App = () => <SlQrCode value=\"https://shoelace.style/\" size=\"64\" />;\n```\n\n### Radius\n\nCreate a rounded effect with the `radius` attribute.\n\n```html:preview\n<sl-qr-code value=\"https://shoelace.style/\" radius=\"0.5\"></sl-qr-code>\n```\n\n```jsx:react\nimport SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';\n\nconst App = () => <SlQrCode value=\"https://shoelace.style/\" radius=\"0.5\" />;\n```\n\n### Error Correction\n\nQR codes can be rendered with various levels of [error correction](https://www.qrcode.com/en/about/error_correction.html) that can be set using the `error-correction` attribute. This example generates four codes with the same value using different error correction levels.\n\n```html:preview\n<div class=\"qr-error-correction\">\n  <sl-qr-code value=\"https://shoelace.style/\" error-correction=\"L\"></sl-qr-code>\n  <sl-qr-code value=\"https://shoelace.style/\" error-correction=\"M\"></sl-qr-code>\n  <sl-qr-code value=\"https://shoelace.style/\" error-correction=\"Q\"></sl-qr-code>\n  <sl-qr-code value=\"https://shoelace.style/\" error-correction=\"H\"></sl-qr-code>\n</div>\n\n<style>\n  .qr-error-correction {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlQrCode from '@shoelace-style/shoelace/dist/react/qr-code';\n\nconst css = `\n  .qr-error-correction {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n  }\n`;\n\nconst App = () => {\n  return (\n    <>\n      <div className=\"qr-error-correction\">\n        <SlQrCode value=\"https://shoelace.style/\" error-correction=\"L\" />\n        <SlQrCode value=\"https://shoelace.style/\" error-correction=\"M\" />\n        <SlQrCode value=\"https://shoelace.style/\" error-correction=\"Q\" />\n        <SlQrCode value=\"https://shoelace.style/\" error-correction=\"H\" />\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/radio-button.md",
    "content": "---\nmeta:\n  title: Radio Button\n  description: Radios buttons allow the user to select a single option from a group using a button-like control.\nlayout: component\n---\n\nRadio buttons are designed to be used with [radio groups](/components/radio-group). When a radio button has focus, the arrow keys can be used to change the selected option just like standard radio controls.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n## Examples\n\n### Checked States\n\nTo set the initial value and checked state, use the `value` attribute on the containing radio group.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a radio button.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\" disabled>Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\" disabled>\n      Option 2\n    </SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Sizes\n\nUse the `size` attribute to change a radio button's size.\n\n```html:preview\n<sl-radio-group size=\"small\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"medium\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"large\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup size=\"small\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n\n  <br />\n\n  <SlRadioGroup size=\"medium\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n\n  <br />\n\n  <SlRadioGroup size=\"large\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Pill Buttons\n\nUse the `pill` attribute to give radio buttons rounded edges.\n\n```html:preview\n<sl-radio-group size=\"small\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button pill value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button pill value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button pill value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"medium\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button pill value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button pill value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button pill value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"large\" label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button pill value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button pill value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button pill value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup size=\"small\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton pill value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton pill value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton pill value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n\n  <br />\n\n  <SlRadioGroup size=\"medium\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton pill value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton pill value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton pill value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n\n  <br />\n\n  <SlRadioGroup size=\"large\" label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton pill value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton pill value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton pill value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Prefix and Suffix Icons\n\nUse the `prefix` and `suffix` slots to add icons.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">\n    <sl-icon slot=\"prefix\" name=\"archive\"></sl-icon>\n    Option 1\n  </sl-radio-button>\n\n  <sl-radio-button value=\"2\">\n    <sl-icon slot=\"suffix\" name=\"bag\"></sl-icon>\n    Option 2\n  </sl-radio-button>\n\n  <sl-radio-button value=\"3\">\n    <sl-icon slot=\"prefix\" name=\"gift\"></sl-icon>\n    <sl-icon slot=\"suffix\" name=\"cart\"></sl-icon>\n    Option 3\n  </sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">\n      <SlIcon slot=\"prefix\" name=\"archive\" />\n      Option 1\n    </SlRadioButton>\n\n    <SlRadioButton value=\"2\">\n      <SlIcon slot=\"suffix\" name=\"bag\" />\n      Option 2\n    </SlRadioButton>\n\n    <SlRadioButton value=\"3\">\n      <SlIcon slot=\"prefix\" name=\"gift\" />\n      <SlIcon slot=\"suffix\" name=\"cart\" />\n      Option 3\n    </SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Buttons with Icons\n\nYou can omit button labels and use icons instead. Make sure to set a `label` attribute on each icon so screen readers will announce each option correctly.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"neutral\">\n  <sl-radio-button value=\"angry\">\n    <sl-icon name=\"emoji-angry\" label=\"Angry\"></sl-icon>\n  </sl-radio-button>\n\n  <sl-radio-button value=\"sad\">\n    <sl-icon name=\"emoji-frown\" label=\"Sad\"></sl-icon>\n  </sl-radio-button>\n\n  <sl-radio-button value=\"neutral\">\n    <sl-icon name=\"emoji-neutral\" label=\"Neutral\"></sl-icon>\n  </sl-radio-button>\n\n  <sl-radio-button value=\"happy\">\n    <sl-icon name=\"emoji-smile\" label=\"Happy\"></sl-icon>\n  </sl-radio-button>\n\n  <sl-radio-button value=\"laughing\">\n    <sl-icon name=\"emoji-laughing\" label=\"Laughing\"></sl-icon>\n  </sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"neutral\">\n    <SlRadioButton value=\"angry\">\n      <SlIcon name=\"emoji-angry\" label=\"Angry\" />\n    </SlRadioButton>\n\n    <SlRadioButton value=\"sad\">\n      <SlIcon name=\"emoji-frown\" label=\"Sad\" />\n    </SlRadioButton>\n\n    <SlRadioButton value=\"neutral\">\n      <SlIcon name=\"emoji-neutral\" label=\"Neutral\" />\n    </SlRadioButton>\n\n    <SlRadioButton value=\"happy\">\n      <SlIcon name=\"emoji-smile\" label=\"Happy\" />\n    </SlRadioButton>\n\n    <SlRadioButton value=\"laughing\">\n      <SlIcon name=\"emoji-laughing\" label=\"Laughing\" />\n    </SlRadioButton>\n  </SlRadioGroup>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/radio-group.md",
    "content": "---\nmeta:\n  title: Radio Group\n  description: Radio groups are used to group multiple radios or radio buttons so they function as a single form control.\nlayout: component\n---\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\">Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\">Option 2</SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n## Examples\n\n### Help Text\n\nAdd descriptive help text to a radio group with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" help-text=\"Choose the most appropriate option.\" name=\"a\" value=\"1\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\">Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" help-text=\"Choose the most appropriate option.\" name=\"a\" value=\"1\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\">Option 2</SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n### Radio Buttons\n\n[Radio buttons](/components/radio-button) offer an alternate way to display radio controls. In this case, an internal [button group](/components/button-group) is used to group the buttons into a single, cohesive control.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" help-text=\"Select an option that makes you proud.\" name=\"a\" value=\"1\">\n  <sl-radio-button value=\"1\">Option 1</sl-radio-button>\n  <sl-radio-button value=\"2\">Option 2</sl-radio-button>\n  <sl-radio-button value=\"3\">Option 3</sl-radio-button>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadioButton from '@shoelace-style/shoelace/dist/react/radio-button';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadioButton value=\"1\">Option 1</SlRadioButton>\n    <SlRadioButton value=\"2\">Option 2</SlRadioButton>\n    <SlRadioButton value=\"3\">Option 3</SlRadioButton>\n  </SlRadioGroup>\n);\n```\n\n### Disabling Options\n\nRadios and radio buttons can be disabled by adding the `disabled` attribute to the respective options inside the radio group.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\" disabled>Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\" disabled>\n      Option 2\n    </SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n### Sizing Options\n\nThe size of [Radios](/components/radio) and [Radio Buttons](/components/radio-buttons) will be determined by the Radio Group's `size` attribute.\n\n```html preview\n<sl-radio-group label=\"Select an option\" size=\"medium\" value=\"medium\" class=\"radio-group-size\">\n  <sl-radio value=\"small\">Small</sl-radio>\n  <sl-radio value=\"medium\">Medium</sl-radio>\n  <sl-radio value=\"large\">Large</sl-radio>\n</sl-radio-group>\n\n<script>\n  const radioGroup = document.querySelector('.radio-group-size');\n\n  radioGroup.addEventListener('sl-change', () => {\n    radioGroup.size = radioGroup.value;\n  });\n</script>\n```\n\n```jsx react\nimport { useState } from 'react';\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => {\n  const [size, setSize] = useState('medium');\n\n  return (\n    <>\n      <SlRadioGroup\n        label=\"Select an option\"\n        size={size}\n        value={size}\n        class=\"radio-group-size\"\n        onSlChange={event => setSize(event.target.value)}\n      >\n        <SlRadio value=\"small\">Small</SlRadio>\n        <SlRadio value=\"medium\">Medium</SlRadio>\n        <SlRadio value=\"large\">Large</SlRadio>\n      </SlRadioGroup>\n    </>\n  );\n};\n```\n\n:::tip\n[Radios](/components/radio) and [Radio Buttons](/components/radio-button) also have a `size` attribute. This can be useful in certain compositions, but it will be ignored when used inside of a Radio Group.\n:::\n\n### Validation\n\nSetting the `required` attribute to make selecting an option mandatory. If a value has not been selected, it will prevent the form from submitting and display an error message.\n\n```html:preview\n<form class=\"validation\">\n  <sl-radio-group label=\"Select an option\" name=\"a\" required>\n    <sl-radio value=\"1\">Option 1</sl-radio>\n    <sl-radio value=\"2\">Option 2</sl-radio>\n    <sl-radio value=\"3\">Option 3</sl-radio>\n  </sl-radio-group>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n</form>\n\n<script>\n  const form = document.querySelector('.validation');\n\n  // Handle form submit\n  form.addEventListener('submit', event => {\n    event.preventDefault();\n    alert('All fields are valid!');\n  });\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\nconst App = () => {\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  return (\n    <form class=\"custom-validity\" onSubmit={handleSubmit}>\n      <SlRadioGroup label=\"Select an option\" name=\"a\" required onSlChange={handleChange}>\n        <SlRadio value=\"1\">\n          Option 1\n        </SlRadio>\n        <SlRadiovalue=\"2\">\n          Option 2\n        </SlRadio>\n        <SlRadio value=\"3\">\n          Option 3\n        </SlRadio>\n      </SlRadioGroup>\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n### Custom Validity\n\nUse the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.\n\n```html:preview\n<form class=\"custom-validity\">\n  <sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n    <sl-radio value=\"1\">Not me</sl-radio>\n    <sl-radio value=\"2\">Me neither</sl-radio>\n    <sl-radio value=\"3\">Choose me</sl-radio>\n  </sl-radio-group>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n</form>\n\n<script>\n  const form = document.querySelector('.custom-validity');\n  const radioGroup = form.querySelector('sl-radio-group');\n  const errorMessage = 'You must choose the last option';\n\n  // Set initial validity as soon as the element is defined\n  customElements.whenDefined('sl-radio').then(() => {\n    radioGroup.setCustomValidity(errorMessage);\n  });\n\n  // Update validity when a selection is made\n  form.addEventListener('sl-change', () => {\n    const isValid = radioGroup.value === '3';\n    radioGroup.setCustomValidity(isValid ? '' : errorMessage);\n  });\n\n  // Handle form submit\n  form.addEventListener('submit', event => {\n    event.preventDefault();\n    alert('All fields are valid!');\n  });\n</script>\n```\n\n```jsx:react\nimport { useEffect, useRef } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\nconst App = () => {\n  const radioGroup = useRef(null);\n  const errorMessage = 'You must choose this option';\n\n  function handleChange() {\n    radioGroup.current.setCustomValidity(radioGroup.current.value === '3' ? '' : errorMessage);\n  }\n\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  useEffect(() => {\n    radio.current.setCustomValidity(errorMessage);\n  }, []);\n\n  return (\n    <form class=\"custom-validity\" onSubmit={handleSubmit}>\n      <SlRadioGroup ref={radioGroup} label=\"Select an option\" name=\"a\" value=\"1\" onSlChange={handleChange}>\n        <SlRadio value=\"1\">Not me</SlRadio>\n        <SlRadio value=\"2\">Me neither</SlRadio>\n        <SlRadio value=\"3\">Choose me</SlRadio>\n      </SlRadioGroup>\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/radio.md",
    "content": "---\nmeta:\n  title: Radio\n  description: Radios allow the user to select a single option from a group.\nlayout: component\n---\n\nRadios are designed to be used with [radio groups](/components/radio-group).\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\">Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\">Option 2</SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Initial Value\n\nTo set the initial value and checked state, use the `value` attribute on the containing radio group.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"3\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\">Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"3\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\">Option 2</SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a radio.\n\n```html:preview\n<sl-radio-group label=\"Select an option\" name=\"a\" value=\"1\">\n  <sl-radio value=\"1\">Option 1</sl-radio>\n  <sl-radio value=\"2\" disabled>Option 2</sl-radio>\n  <sl-radio value=\"3\">Option 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx:react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\nimport SlRadioGroup from '@shoelace-style/shoelace/dist/react/radio-group';\n\nconst App = () => (\n  <SlRadioGroup label=\"Select an option\" name=\"a\" value=\"1\">\n    <SlRadio value=\"1\">Option 1</SlRadio>\n    <SlRadio value=\"2\" disabled>\n      Option 2\n    </SlRadio>\n    <SlRadio value=\"3\">Option 3</SlRadio>\n  </SlRadioGroup>\n);\n```\n\n## Sizes\n\nAdd the `size` attribute to the [Radio Group](/components/radio-group) to change the radios' size.\n\n```html preview\n<sl-radio-group size=\"small\" value=\"1\">\n  <sl-radio value=\"1\">Small 1</sl-radio>\n  <sl-radio value=\"2\">Small 2</sl-radio>\n  <sl-radio value=\"3\">Small 3</sl-radio>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"medium\" value=\"1\">\n  <sl-radio value=\"1\">Medium 1</sl-radio>\n  <sl-radio value=\"2\">Medium 2</sl-radio>\n  <sl-radio value=\"3\">Medium 3</sl-radio>\n</sl-radio-group>\n\n<br />\n\n<sl-radio-group size=\"large\" value=\"1\">\n  <sl-radio value=\"1\">Large 1</sl-radio>\n  <sl-radio value=\"2\">Large 2</sl-radio>\n  <sl-radio value=\"3\">Large 3</sl-radio>\n</sl-radio-group>\n```\n\n```jsx react\nimport SlRadio from '@shoelace-style/shoelace/dist/react/radio';\n\nconst App = () => (\n  <>\n    <SlRadioGroup size=\"small\" value=\"1\">\n      <SlRadio value=\"1\">Small 1</SlRadio>\n      <SlRadio value=\"2\">Small 2</SlRadio>\n      <SlRadio value=\"3\">Small 3</SlRadio>\n    </SlRadioGroup>\n\n    <br />\n\n    <SlRadioGroup size=\"medium\" value=\"1\">\n      <SlRadio value=\"1\">Medium 1</SlRadio>\n      <SlRadio value=\"2\">Medium 2</SlRadio>\n      <SlRadio value=\"3\">Medium 3</SlRadio>\n    </SlRadioGroup>\n\n    <br />\n\n    <SlRadioGroup size=\"large\" value=\"1\">\n      <SlRadio value=\"1\">Large 1</SlRadio>\n      <SlRadio value=\"2\">Large 2</SlRadio>\n      <SlRadio value=\"3\">Large 3</SlRadio>\n    </SlRadioGroup>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/range.md",
    "content": "---\nmeta:\n  title: Range\n  description: Ranges allow the user to select a single value within a given range using a slider.\nlayout: component\n---\n\n```html:preview\n<sl-range></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange />;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Labels\n\nUse the `label` attribute to give the range an accessible label. For labels that contain HTML, use the `label` slot instead.\n\n```html:preview\n<sl-range label=\"Volume\" min=\"0\" max=\"100\"></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange label=\"Volume\" min={0} max={100} />;\n```\n\n### Help Text\n\nAdd descriptive help text to a range with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-range label=\"Volume\" help-text=\"Controls the volume of the current song.\" min=\"0\" max=\"100\"></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange label=\"Volume\" help-text=\"Controls the volume of the current song.\" min={0} max={100} />;\n```\n\n### Min, Max, and Step\n\nUse the `min` and `max` attributes to set the range's minimum and maximum values, respectively. The `step` attribute determines the value's interval when increasing and decreasing.\n\n```html:preview\n<sl-range min=\"0\" max=\"10\" step=\"1\"></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange min={0} max={10} step={1} />;\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a slider.\n\n```html:preview\n<sl-range disabled></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange disabled />;\n```\n\n### Tooltip Placement\n\nBy default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider.\n\n```html:preview\n<sl-range tooltip=\"bottom\"></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange tooltip=\"bottom\" />;\n```\n\n### Disable the Tooltip\n\nTo disable the tooltip, set `tooltip` to `none`.\n\n```html:preview\n<sl-range tooltip=\"none\"></sl-range>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange tooltip=\"none\" />;\n```\n\n### Custom Track Colors\n\nYou can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties.\n\n```html:preview\n<sl-range\n  style=\"\n  --track-color-active: var(--sl-color-primary-600);\n  --track-color-inactive: var(--sl-color-primary-100);\n\"\n></sl-range>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => (\n  <SlRange\n    style={{\n      '--track-color-active': 'var(--sl-color-primary-600)',\n      '--track-color-inactive': 'var(--sl-color-primary-200)'\n    }}\n  />\n);\n```\n\n{% endraw %}\n\n### Custom Track Offset\n\nYou can customize the initial offset of the active track using the `--track-active-offset` custom property.\n\n```html:preview\n<sl-range\n  min=\"-100\"\n  max=\"100\"\n  style=\"\n  --track-color-active: var(--sl-color-primary-600);\n  --track-color-inactive: var(--sl-color-primary-100);\n  --track-active-offset: 50%;\n\"\n></sl-range>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => (\n  <SlRange\n    min={-100}\n    max={100}\n    style={{\n      '--track-color-active': 'var(--sl-color-primary-600)',\n      '--track-color-inactive': 'var(--sl-color-primary-200)',\n      '--track-active-offset': '50%'\n    }}\n  />\n);\n```\n\n{% endraw %}\n\n### Custom Tooltip Formatter\n\nYou can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument.\n\n```html:preview\n<sl-range min=\"0\" max=\"100\" step=\"1\" class=\"range-with-custom-formatter\"></sl-range>\n\n<script>\n  const range = document.querySelector('.range-with-custom-formatter');\n  range.tooltipFormatter = value => `Total - ${value}%`;\n</script>\n```\n\n```jsx:react\nimport SlRange from '@shoelace-style/shoelace/dist/react/range';\n\nconst App = () => <SlRange min={0} max={100} step={1} tooltipFormatter={value => `Total - ${value}%`} />;\n```\n"
  },
  {
    "path": "docs/pages/components/rating.md",
    "content": "---\nmeta:\n  title: Rating\n  description: Ratings give users a way to quickly view and provide feedback.\nlayout: component\n---\n\n```html:preview\n<sl-rating label=\"Rating\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" />;\n```\n\n## Examples\n\n### Labels\n\nRatings are commonly identified contextually, so labels aren't displayed. However, you should always provide one for assistive devices using the `label` attribute.\n\n```html:preview\n<sl-rating label=\"Rate this component\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rate this component\" />;\n```\n\n### Maximum Value\n\nRatings are 0-5 by default. To change the maximum possible value, use the `max` attribute.\n\n```html:preview\n<sl-rating label=\"Rating\" max=\"3\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" max={3} />;\n```\n\n### Precision\n\nUse the `precision` attribute to let users select fractional ratings.\n\n```html:preview\n<sl-rating label=\"Rating\" precision=\"0.5\" value=\"2.5\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" precision={0.5} value={2.5} />;\n```\n\n### Symbol Sizes\n\nSet the `--symbol-size` custom property to adjust the size.\n\n```html:preview\n<sl-rating label=\"Rating\" style=\"--symbol-size: 2rem;\"></sl-rating>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" style={{ '--symbol-size': '2rem' }} />;\n```\n\n{% endraw %}\n\n### Readonly\n\nUse the `readonly` attribute to display a rating that users can't change.\n\n```html:preview\n<sl-rating label=\"Rating\" readonly value=\"3\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" readonly value={3} />;\n```\n\n### Disabled\n\nUse the `disable` attribute to disable the rating.\n\n```html:preview\n<sl-rating label=\"Rating\" disabled value=\"3\"></sl-rating>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => <SlRating label=\"Rating\" disabled value={3} />;\n```\n\n### Detecting Hover\n\nUse the `sl-hover` event to detect when the user hovers over (or touch and drag) the rating. This lets you hook into values as the user interacts with the rating, but before they select a value.\n\nThe event has a payload with `phase` and `value` properties. The `phase` property tells when hovering starts, moves to a new value, and ends. The `value` property tells what the rating's value would be if the user were to commit to the hovered value.\n\n```html:preview\n<div class=\"detect-hover\">\n  <sl-rating label=\"Rating\"></sl-rating>\n  <span></span>\n</div>\n\n<script>\n  const rating = document.querySelector('.detect-hover > sl-rating');\n  const span = rating.nextElementSibling;\n  const terms = ['No rating', 'Terrible', 'Bad', 'OK', 'Good', 'Excellent'];\n\n  rating.addEventListener('sl-hover', event => {\n    span.textContent = terms[event.detail.value];\n\n    // Clear feedback when hovering stops\n    if (event.detail.phase === 'end') {\n      span.textContent = '';\n    }\n  });\n</script>\n\n<style>\n  .detect-hover span {\n    position: relative;\n    top: -4px;\n    left: 8px;\n    border-radius: var(--sl-border-radius-small);\n    background: var(--sl-color-neutral-900);\n    color: var(--sl-color-neutral-0);\n    text-align: center;\n    padding: 4px 6px;\n  }\n\n  .detect-hover span:empty {\n    display: none;\n  }\n</style>\n```\n\n```jsx:react\nimport { useState } from 'react';\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst terms = ['No rating', 'Terrible', 'Bad', 'OK', 'Good', 'Excellent'];\nconst css = `\n  .detect-hover span {\n    position: relative;\n    top: -4px;\n    left: 8px;\n    border-radius: var(--sl-border-radius-small);\n    background: var(--sl-color-neutral-900);\n    color: var(--sl-color-neutral-0);\n    text-align: center;\n    padding: 4px 6px;\n  }\n\n  .detect-hover span:empty {\n    display: none;\n  }\n`;\n\nfunction handleHover(event) {\n  rating.addEventListener('sl-hover', event => {\n    setFeedback(terms[event.detail.value]);\n\n    // Clear feedback when hovering stops\n    if (event.detail.phase === 'end') {\n      setFeedback('');\n    }\n  });\n}\n\nconst App = () => {\n  const [feedback, setFeedback] = useState(true);\n\n  return (\n    <>\n      <div class=\"detect-hover\">\n        <SlRating label=\"Rating\" onSlHover={handleHover} />\n        <span>{feedback}</span>\n      </div>\n      <style>{css}</style>\n    </>\n  );\n};\n```\n\n### Custom Icons\n\nYou can provide custom icons by passing a function to the `getSymbol` property.\n\n```html:preview\n<sl-rating label=\"Rating\" class=\"rating-hearts\" style=\"--symbol-color-active: #ff4136;\"></sl-rating>\n\n<script>\n  const rating = document.querySelector('.rating-hearts');\n  rating.getSymbol = () => '<sl-icon name=\"heart-fill\"></sl-icon>';\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nconst App = () => (\n  <SlRating\n    label=\"Rating\"\n    getSymbol={() => '<sl-icon name=\"heart-fill\"></sl-icon>'}\n    style={{ '--symbol-color-active': '#ff4136' }}\n  />\n);\n```\n\n{% endraw %}\n\n### Value-based Icons\n\nYou can also use the `getSymbol` property to render different icons based on value.\n\n```html:preview\n<sl-rating label=\"Rating\" class=\"rating-emojis\"></sl-rating>\n\n<script>\n  const rating = document.querySelector('.rating-emojis');\n\n  rating.getSymbol = value => {\n    const icons = ['emoji-angry', 'emoji-frown', 'emoji-expressionless', 'emoji-smile', 'emoji-laughing'];\n    return `<sl-icon name=\"${icons[value - 1]}\"></sl-icon>`;\n  };\n</script>\n```\n\n```jsx:react\nimport SlRating from '@shoelace-style/shoelace/dist/react/rating';\n\nfunction getSymbol(value) {\n  const icons = ['emoji-angry', 'emoji-frown', 'emoji-expressionless', 'emoji-smile', 'emoji-laughing'];\n  return `<sl-icon name=\"${icons[value - 1]}\"></sl-icon>`;\n}\n\nconst App = () => <SlRating label=\"Rating\" getSymbol={getSymbol} />;\n```\n"
  },
  {
    "path": "docs/pages/components/relative-time.md",
    "content": "---\nmeta:\n  title: Relative Time\n  description: Outputs a localized time phrase relative to the current date and time.\nlayout: component\n---\n\nLocalization is handled by the browser's [`Intl.RelativeTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat). No language packs are required.\n\n```html:preview\n<!-- Shoelace 2 release date 🎉 -->\n<sl-relative-time date=\"2020-07-15T09:17:00-04:00\"></sl-relative-time>\n```\n\n```jsx:react\nimport SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';\n\nconst App = () => <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" />;\n```\n\nThe `date` attribute determines when the date/time is calculated from. It must be a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object set via JavaScript.\n\n:::tip\nWhen using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, always use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure the date will be parsed properly by all clients.\n:::\n\n## Examples\n\n### Keeping Time in Sync\n\nUse the `sync` attribute to update the displayed value automatically as time passes.\n\n```html:preview\n<div class=\"relative-time-sync\">\n  <sl-relative-time sync></sl-relative-time>\n</div>\n\n<script>\n  const container = document.querySelector('.relative-time-sync');\n  const relativeTime = container.querySelector('sl-relative-time');\n\n  relativeTime.date = new Date(new Date().getTime() - 60000);\n</script>\n```\n\n```jsx:react\nimport SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';\n\nconst date = new Date(new Date().getTime() - 60000);\n\nconst App = () => <SlRelativeTime date={date} sync />;\n```\n\n### Formatting Styles\n\nYou can change how the time is displayed using the `format` attribute. Note that some locales may display the same values for `narrow` and `short` formats.\n\n```html:preview\n<sl-relative-time date=\"2020-07-15T09:17:00-04:00\" format=\"narrow\"></sl-relative-time><br />\n<sl-relative-time date=\"2020-07-15T09:17:00-04:00\" format=\"short\"></sl-relative-time><br />\n<sl-relative-time date=\"2020-07-15T09:17:00-04:00\" format=\"long\"></sl-relative-time>\n```\n\n```jsx:react\nimport SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';\n\nconst App = () => (\n  <>\n    <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" format=\"narrow\" />\n    <br />\n    <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" format=\"short\" />\n    <br />\n    <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" format=\"long\" />\n  </>\n);\n```\n\n### Localization\n\nUse the `lang` attribute to set the desired locale.\n\n```html:preview\nEnglish: <sl-relative-time date=\"2020-07-15T09:17:00-04:00\" lang=\"en-US\"></sl-relative-time><br />\nChinese: <sl-relative-time date=\"2020-07-15T09:17:00-04:00\" lang=\"zh-CN\"></sl-relative-time><br />\nGerman: <sl-relative-time date=\"2020-07-15T09:17:00-04:00\" lang=\"de\"></sl-relative-time><br />\nGreek: <sl-relative-time date=\"2020-07-15T09:17:00-04:00\" lang=\"el\"></sl-relative-time><br />\nRussian: <sl-relative-time date=\"2020-07-15T09:17:00-04:00\" lang=\"ru\"></sl-relative-time>\n```\n\n```jsx:react\nimport SlRelativeTime from '@shoelace-style/shoelace/dist/react/relative-time';\n\nconst App = () => (\n  <>\n    English: <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" lang=\"en-US\" />\n    <br />\n    Chinese: <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" lang=\"zh-CN\" />\n    <br />\n    German: <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" lang=\"de\" />\n    <br />\n    Greek: <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" lang=\"el\" />\n    <br />\n    Russian: <SlRelativeTime date=\"2020-07-15T09:17:00-04:00\" lang=\"ru\" />\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/resize-observer.md",
    "content": "---\nmeta:\n  title: Resize Observer\n  description: The Resize Observer component offers a thin, declarative interface to the ResizeObserver API.\nlayout: component\n---\n\nThe resize observer will report changes to the dimensions of the elements it wraps through the `sl-resize` event. When emitted, a collection of [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) objects will be attached to `event.detail` that contains the target element and information about its dimensions.\n\n```html:preview\n<div class=\"resize-observer-overview\">\n  <sl-resize-observer>\n    <div>Resize this box and watch the console 👉</div>\n  </sl-resize-observer>\n</div>\n\n<script>\n  const container = document.querySelector('.resize-observer-overview');\n  const resizeObserver = container.querySelector('sl-resize-observer');\n\n  resizeObserver.addEventListener('sl-resize', event => {\n    console.log(event.detail);\n  });\n</script>\n\n<style>\n  .resize-observer-overview div {\n    display: flex;\n    border: solid 2px var(--sl-input-border-color);\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    padding: 4rem 2rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlResizeObserver from '@shoelace-style/shoelace/dist/react/resize-observer';\n\nconst css = `\n  .resize-observer-overview div {\n    display: flex;\n    border: solid 2px var(--sl-input-border-color);\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    padding: 4rem 2rem;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"resize-observer-overview\">\n      <SlResizeObserver onSlResize={event => console.log(event.detail)}>\n        <div>Resize this box and watch the console 👉</div>\n      </SlResizeObserver>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/select.md",
    "content": "---\nmeta:\n  title: Select\n  description: Selects allow you to choose items from a menu of predefined options.\nlayout: component\n---\n\n```html:preview\n<sl-select>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n  <sl-option value=\"option-4\">Option 4</sl-option>\n  <sl-option value=\"option-5\">Option 5</sl-option>\n  <sl-option value=\"option-6\">Option 6</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n    <SlOption value=\"option-4\">Option 4</SlOption>\n    <SlOption value=\"option-5\">Option 5</SlOption>\n    <SlOption value=\"option-6\">Option 6</SlOption>\n  </SlSelect>\n);\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Labels\n\nUse the `label` attribute to give the select an accessible label. For labels that contain HTML, use the `label` slot instead.\n\n```html:preview\n<sl-select label=\"Select one\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect label=\"Select one\">\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Help Text\n\nAdd descriptive help text to a select with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-select label=\"Experience\" help-text=\"Please tell us your skill level.\">\n  <sl-option value=\"1\">Novice</sl-option>\n  <sl-option value=\"2\">Intermediate</sl-option>\n  <sl-option value=\"3\">Advanced</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect label=\"Experience\" help-text=\"Please tell us your skill level.\">\n    <SlOption value=\"1\">Novice</SlOption>\n    <SlOption value=\"2\">Intermediate</SlOption>\n    <SlOption value=\"3\">Advanced</SlOption>\n  </SlSelect>\n);\n```\n\n### Placeholders\n\nUse the `placeholder` attribute to add a placeholder.\n\n```html:preview\n<sl-select placeholder=\"Select one\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect placeholder=\"Select one\">\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Clearable\n\nUse the `clearable` attribute to make the control clearable. The clear button only appears when an option is selected.\n\n```html:preview\n<sl-select clearable value=\"option-1\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect placeholder=\"Clearable\" clearable>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Filled Selects\n\nAdd the `filled` attribute to draw a filled select.\n\n```html:preview\n<sl-select filled>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect filled>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Pill\n\nUse the `pill` attribute to give selects rounded edges.\n\n```html:preview\n<sl-select pill>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect pill>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a select.\n\n```html:preview\n<sl-select placeholder=\"Disabled\" disabled>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect placeholder=\"Disabled\" disabled>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Multiple\n\nTo allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `clearable` when this option is enabled. To set multiple values at once, set `value` to a space-delimited list of values.\n\n```html:preview\n<sl-select label=\"Select a Few\" value=\"option-1 option-2 option-3\" multiple clearable>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n  <sl-option value=\"option-4\">Option 4</sl-option>\n  <sl-option value=\"option-5\">Option 5</sl-option>\n  <sl-option value=\"option-6\">Option 6</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect label=\"Select a Few\" value={[\"option-1\", \"option-2\", \"option-3\"]} multiple clearable>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n    <SlOption value=\"option-4\">Option 4</SlOption>\n    <SlOption value=\"option-5\">Option 5</SlOption>\n    <SlOption value=\"option-6\">Option 6</SlOption>\n  </SlSelect>\n);\n```\n\n:::tip\nNote that multi-select options may wrap, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.\n:::\n\n### Setting Initial Values\n\nUse the `value` attribute to set the initial selection.\n\nWhen using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. Because of this, `<sl-option>` values cannot contain spaces. If you're accessing the `value` _property_ through Javascript, it will be an array.\n\n```html:preview\n<sl-select value=\"option-1 option-2\" multiple clearable>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n  <sl-option value=\"option-4\">Option 4</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlDivider from '@shoelace-style/shoelace/dist/react/divider';\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect value={[\"option-1\", \"option-2\"]} multiple clearable>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlSelect>\n);\n```\n\n### Grouping Options\n\nUse `<sl-divider>` to group listbox items visually. You can also use `<small>` to provide labels, but they won't be announced by most assistive devices.\n\n```html:preview\n<sl-select>\n  <small>Section 1</small>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n  <sl-divider></sl-divider>\n  <small>Section 2</small>\n  <sl-option value=\"option-4\">Option 4</sl-option>\n  <sl-option value=\"option-5\">Option 5</sl-option>\n  <sl-option value=\"option-6\">Option 6</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect>\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n    <SlOption value=\"option-4\">Option 4</SlOption>\n    <SlOption value=\"option-5\">Option 5</SlOption>\n    <SlOption value=\"option-6\">Option 6</SlOption>\n  </SlSelect>\n);\n```\n\n### Sizes\n\nUse the `size` attribute to change a select's size. Note that size does not apply to listbox options.\n\n```html:preview\n<sl-select placeholder=\"Small\" size=\"small\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n\n<br />\n\n<sl-select placeholder=\"Medium\" size=\"medium\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n\n<br />\n\n<sl-select placeholder=\"Large\" size=\"large\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <>\n    <SlSelect placeholder=\"Small\" size=\"small\">\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n\n    <br />\n\n    <SlSelect placeholder=\"Medium\" size=\"medium\">\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n\n    <br />\n\n    <SlSelect placeholder=\"Large\" size=\"large\">\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n  </>\n);\n```\n\n### Placement\n\nThe preferred placement of the select's listbox can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport. Valid placements are `top` and `bottom`.\n\n```html:preview\n<sl-select placement=\"top\">\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <SlSelect placement=\"top\">\n    <SlOption value=\"option-1\">Option 1</SlOption>\n    <SlOption value=\"option-2\">Option 2</SlOption>\n    <SlOption value=\"option-3\">Option 3</SlOption>\n  </SlDropdown>\n);\n```\n\n### Prefix & Suffix\n\nUse the `prefix` and `suffix` slots to add presentational icons and text. Avoid slotting in interactive elements, such as buttons, links, etc.\n\n```html:preview\n<sl-select placeholder=\"Small\" size=\"small\" clearable>\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-badge slot=\"suffix\">New</sl-badge>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n<br />\n<sl-select placeholder=\"Medium\" size=\"medium\" clearable>\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-badge slot=\"suffix\">New</sl-badge>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n<br />\n<sl-select placeholder=\"Large\" size=\"large\" clearable>\n  <sl-icon name=\"house\" slot=\"prefix\"></sl-icon>\n  <sl-badge slot=\"suffix\">New</sl-badge>\n  <sl-option value=\"option-1\">Option 1</sl-option>\n  <sl-option value=\"option-2\">Option 2</sl-option>\n  <sl-option value=\"option-3\">Option 3</sl-option>\n</sl-select>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlOption from '@shoelace-style/shoelace/dist/react/option';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\n\nconst App = () => (\n  <>\n    <SlSelect placeholder=\"Small\" size=\"small\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n    <br />\n    <SlSelect placeholder=\"Medium\" size=\"medium\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n    <br />\n    <SlSelect placeholder=\"Large\" size=\"large\">\n      <SlIcon name=\"house\" slot=\"prefix\"></SlIcon>\n      <SlOption value=\"option-1\">Option 1</SlOption>\n      <SlOption value=\"option-2\">Option 2</SlOption>\n      <SlOption value=\"option-3\">Option 3</SlOption>\n    </SlSelect>\n  </>\n);\n```\n\n### Custom Tags\n\nWhen multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your function can return a string of HTML, a <a href=\"https://lit.dev/docs/templates/overview/\">Lit Template</a>, or an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). The `getTag()` function will be called for each option. The first argument is an `<sl-option>` element and the second argument is the tag's index (its position in the tag list).\n\nRemember that custom tags are rendered in a shadow root. To style them, you can use the `style` attribute in your template or you can add your own [parts](/getting-started/customizing/#css-parts) and target them with the [`::part()`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) selector.\n\n```html:preview\n<sl-select\n  placeholder=\"Select one\"\n  value=\"email phone\"\n  multiple\n  clearable\n  class=\"custom-tag\"\n>\n  <sl-option value=\"email\">\n    <sl-icon slot=\"prefix\" name=\"envelope\"></sl-icon>\n    Email\n  </sl-option>\n  <sl-option value=\"phone\">\n    <sl-icon slot=\"prefix\" name=\"telephone\"></sl-icon>\n    Phone\n  </sl-option>\n  <sl-option value=\"chat\">\n    <sl-icon slot=\"prefix\" name=\"chat-dots\"></sl-icon>\n    Chat\n  </sl-option>\n</sl-select>\n\n<script type=\"module\">\n  const select = document.querySelector('.custom-tag');\n\n  select.getTag = (option, index) => {\n    // Use the same icon used in the <sl-option>\n    const name = option.querySelector('sl-icon[slot=\"prefix\"]').name;\n\n    // You can return a string, a Lit Template, or an HTMLElement here\n    return `\n      <sl-tag removable>\n        <sl-icon name=\"${name}\" style=\"padding-inline-end: .5rem;\"></sl-icon>\n        ${option.getTextLabel()}\n      </sl-tag>\n    `;\n  };\n</script>\n```\n\n### Lazy loading options\n\nLazy loading options is very hard to get right. `<sl-select>` largely follows how a native `<select>` works.\n\nHere are the following conditions:\n\n- If a `<sl-select>` is created without any options, but is given a `value` attribute, its `value` will be `\"\"`, and then when options are added, if any of the options have a value equal to the `<sl-select>` value, the value of the `<sl-select>` will equal that of the option.\n\nEX: `<sl-select value=\"foo\">` will have a value of `\"\"` until `<sl-option value=\"foo\">Foo</sl-option>` connects, at which point its value will become `\"foo\"` when submitting.\n\n- If a `<sl-select multiple>` with an initial value has multiple values, but only some of the options are present, it will only respect the options that are present, and if a selected option is loaded in later, _AND_ the value of the select has not changed via user interaction or direct property assignment, it will add the selected option to the form value and to the `.value` of the select.\n\nThis can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<sl-select>` and `<sl-select multiple>` when given initial value attributes. Feel free to play around with it in a codepen.\n\n```html:preview\n<form id=\"lazy-options-example\">\n  <div>\n    <sl-select name=\"select-1\" value=\"foo\" label=\"Single select (with existing options)\">\n      <sl-option value=\"bar\">Bar</sl-option>\n      <sl-option value=\"baz\">Baz</sl-option>\n    </sl-select>\n    <br>\n    <sl-button type=\"button\">Add \"foo\" option</sl-button>\n  </div>\n\n  <br>\n\n  <div>\n    <sl-select name=\"select-2\" value=\"foo\" label=\"Single select (with no existing options)\">\n    </sl-select>\n    <br>\n    <sl-button type=\"button\">Add \"foo\" option</sl-button>\n  </div>\n\n  <br>\n\n  <div>\n    <sl-select name=\"select-3\" value=\"foo bar baz\" multiple label=\"Multiple Select (with existing options)\">\n      <sl-option value=\"bar\">Bar</sl-option>\n      <sl-option value=\"baz\">Baz</sl-option>\n    </sl-select>\n    <br>\n    <sl-button type=\"button\">Add \"foo\" option</sl-button>\n  </div>\n\n  <br>\n\n  <div>\n    <sl-select name=\"select-4\" value=\"foo\" multiple label=\"Multiple Select (with no existing options)\">\n    </sl-select>\n    <br>\n    <sl-button type=\"button\">Add \"foo\" option</sl-button>\n  </div>\n\n  <br><br>\n\n  <div style=\"display: flex; gap: 16px;\">\n    <sl-button type=\"reset\">Reset</sl-button>\n    <sl-button type=\"submit\" variant=\"brand\">Show FormData</sl-button>\n  </div>\n\n  <br>\n\n  <pre hidden><code id=\"lazy-options-example-form-data\"></code></pre>\n\n  <br>\n</form>\n\n<script type=\"module\">\n  function addFooOption(e) {\n    const addFooButton = e.target.closest(\"sl-button[type='button']\")\n    if (!addFooButton) {\n      return\n    }\n    const select = addFooButton.parentElement.querySelector(\"sl-select\")\n    if (select.querySelector(\"sl-option[value='foo']\")) {\n      // Foo already exists. no-op.\n      return\n    }\n    const option = document.createElement(\"sl-option\")\n    option.setAttribute(\"value\", \"foo\")\n    option.innerText = \"Foo\"\n    select.append(option)\n  }\n  function handleLazySubmit (event) {\n    event.preventDefault()\n    const formData = new FormData(event.target)\n    const codeElement = document.querySelector(\"#lazy-options-example-form-data\")\n    const obj = {}\n    for (const key of formData.keys()) {\n      const val = formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key)\n      obj[key] = val\n    }\n    codeElement.textContent = JSON.stringify(obj, null, 2)\n    const preElement = codeElement.parentElement\n    preElement.removeAttribute(\"hidden\")\n  }\n  const container = document.querySelector(\"#lazy-options-example\")\n  container.addEventListener(\"click\", addFooOption)\n  container.addEventListener(\"submit\", handleLazySubmit)\n</script>\n```\n\n:::warning\nBe sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities.\n:::\n"
  },
  {
    "path": "docs/pages/components/skeleton.md",
    "content": "---\nmeta:\n  title: Skeleton\n  description: Skeletons are used to provide a visual representation of where content will eventually be drawn.\nlayout: component\n---\n\nThese are simple containers for scaffolding layouts that mimic what users will see when content has finished loading. This prevents large areas of empty space during asynchronous operations.\n\nSkeletons try not to be opinionated, as there are endless possibilities for designing layouts. Therefore, you'll likely use more than one skeleton to create the effect you want. If you find yourself using them frequently, consider creating a template that renders them with the desired arrangement and styles.\n\n```html:preview\n<div class=\"skeleton-overview\">\n  <header>\n    <sl-skeleton></sl-skeleton>\n    <sl-skeleton></sl-skeleton>\n  </header>\n\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n</div>\n\n<style>\n  .skeleton-overview header {\n    display: flex;\n    align-items: center;\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-overview header sl-skeleton:last-child {\n    flex: 0 0 auto;\n    width: 30%;\n  }\n\n  .skeleton-overview sl-skeleton {\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(1) {\n    float: left;\n    width: 3rem;\n    height: 3rem;\n    margin-right: 1rem;\n    vertical-align: middle;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(3) {\n    width: 95%;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(4) {\n    width: 80%;\n  }\n</style>\n```\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-overview header {\n    display: flex;\n    align-items: center;\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-overview header sl-skeleton:last-child {\n    flex: 0 0 auto;\n    width: 30%;\n  }\n\n  .skeleton-overview sl-skeleton {\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(1) {\n    float: left;\n    width: 3rem;\n    height: 3rem;\n    margin-right: 1rem;\n    vertical-align: middle;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(3) {\n    width: 95%;\n  }\n\n  .skeleton-overview sl-skeleton:nth-child(4) {\n    width: 80%;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"skeleton-overview\">\n      <header>\n        <SlSkeleton />\n        <SlSkeleton />\n      </header>\n\n      <SlSkeleton />\n      <SlSkeleton />\n      <SlSkeleton />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n## Examples\n\n### Effects\n\nThere are two built-in effects, `sheen` and `pulse`. Effects are intentionally subtle, as they can be distracting when used extensively. The default is `none`, which displays a static, non-animated skeleton.\n\n```html:preview\n<div class=\"skeleton-effects\">\n  <sl-skeleton effect=\"none\"></sl-skeleton>\n  None\n\n  <sl-skeleton effect=\"sheen\"></sl-skeleton>\n  Sheen\n\n  <sl-skeleton effect=\"pulse\"></sl-skeleton>\n  Pulse\n</div>\n\n<style>\n  .skeleton-effects {\n    font-size: var(--sl-font-size-small);\n  }\n\n  .skeleton-effects sl-skeleton:not(:first-child) {\n    margin-top: 1rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-effects {\n    font-size: var(--sl-font-size-small);\n  }\n\n  .skeleton-effects sl-skeleton:not(:first-child) {\n    margin-top: 1rem;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"skeleton-effects\">\n      <SlSkeleton effect=\"none\" />\n      None\n      <SlSkeleton effect=\"sheen\" />\n      Sheen\n      <SlSkeleton effect=\"pulse\" />\n      Pulse\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Paragraphs\n\nUse multiple skeletons and some clever styles to simulate paragraphs.\n\n```html:preview\n<div class=\"skeleton-paragraphs\">\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n</div>\n\n<style>\n  .skeleton-paragraphs sl-skeleton {\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-paragraphs sl-skeleton:nth-child(2) {\n    width: 95%;\n  }\n\n  .skeleton-paragraphs sl-skeleton:nth-child(4) {\n    width: 90%;\n  }\n\n  .skeleton-paragraphs sl-skeleton:last-child {\n    width: 50%;\n  }\n</style>\n```\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-paragraphs sl-skeleton {\n    margin-bottom: 1rem;\n  }\n\n  .skeleton-paragraphs sl-skeleton:nth-child(2) {\n    width: 95%;\n  }\n\n  .skeleton-paragraphs sl-skeleton:nth-child(4) {\n    width: 90%;\n  }\n\n  .skeleton-paragraphs sl-skeleton:last-child {\n    width: 50%;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"skeleton-paragraphs\">\n      <SlSkeleton />\n      <SlSkeleton />\n      <SlSkeleton />\n      <SlSkeleton />\n      <SlSkeleton />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Avatars\n\nSet a matching width and height to make a circle, square, or rounded avatar skeleton.\n\n```html:preview\n<div class=\"skeleton-avatars\">\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n  <sl-skeleton></sl-skeleton>\n</div>\n\n<style>\n  .skeleton-avatars sl-skeleton {\n    display: inline-block;\n    width: 3rem;\n    height: 3rem;\n    margin-right: 0.5rem;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(1) {\n    --border-radius: 0;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(2) {\n    --border-radius: var(--sl-border-radius-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-avatars sl-skeleton {\n    display: inline-block;\n    width: 3rem;\n    height: 3rem;\n    margin-right: .5rem;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(1) {\n    --border-radius: 0;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(2) {\n    --border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"skeleton-avatars\">\n      <SlSkeleton />\n      <SlSkeleton />\n      <SlSkeleton />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Custom Shapes\n\nUse the `--border-radius` custom property to make circles, squares, and rectangles. For more complex shapes, you can apply `clip-path` to the `indicator` part. [Try Clippy](https://bennettfeely.com/clippy/) if you need help generating custom shapes.\n\n```html:preview\n<div class=\"skeleton-shapes\">\n  <sl-skeleton class=\"square\"></sl-skeleton>\n  <sl-skeleton class=\"circle\"></sl-skeleton>\n  <sl-skeleton class=\"triangle\"></sl-skeleton>\n  <sl-skeleton class=\"cross\"></sl-skeleton>\n  <sl-skeleton class=\"comment\"></sl-skeleton>\n</div>\n\n<style>\n  .skeleton-shapes sl-skeleton {\n    display: inline-flex;\n    width: 50px;\n    height: 50px;\n  }\n\n  .skeleton-shapes .square::part(indicator) {\n    --border-radius: var(--sl-border-radius-medium);\n  }\n\n  .skeleton-shapes .circle::part(indicator) {\n    --border-radius: var(--sl-border-radius-circle);\n  }\n\n  .skeleton-shapes .triangle::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(50% 0, 0 100%, 100% 100%);\n  }\n\n  .skeleton-shapes .cross::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(\n      20% 0%,\n      0% 20%,\n      30% 50%,\n      0% 80%,\n      20% 100%,\n      50% 70%,\n      80% 100%,\n      100% 80%,\n      70% 50%,\n      100% 20%,\n      80% 0%,\n      50% 30%\n    );\n  }\n\n  .skeleton-shapes .comment::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(0% 0%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%);\n  }\n\n  .skeleton-shapes sl-skeleton:not(:last-child) {\n    margin-right: 0.5rem;\n  }\n</style>\n```\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-shapes sl-skeleton {\n    display: inline-flex;\n    width: 50px;\n    height: 50px;\n  }\n\n  .skeleton-shapes .square::part(indicator) {\n    --border-radius: var(--sl-border-radius-medium);\n  }\n\n  .skeleton-shapes .circle::part(indicator) {\n    --border-radius: var(--sl-border-radius-circle);\n  }\n\n  .skeleton-shapes .triangle::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(50% 0, 0 100%, 100% 100%);\n  }\n\n  .skeleton-shapes .cross::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%);\n  }\n\n  .skeleton-shapes .comment::part(indicator) {\n    --border-radius: 0;\n    clip-path: polygon(0% 0%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%);\n  }\n\n  .skeleton-shapes sl-skeleton:not(:last-child) {\n    margin-right: .5rem;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"skeleton-shapes\">\n      <SlSkeleton className=\"square\" />\n      <SlSkeleton className=\"circle\" />\n      <SlSkeleton className=\"triangle\" />\n      <SlSkeleton className=\"cross\" />\n      <SlSkeleton className=\"comment\" />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Custom Colors\n\nSet the `--color` and `--sheen-color` custom properties to adjust the skeleton's color.\n\n```html:preview\n<sl-skeleton effect=\"sheen\" style=\"--color: tomato; --sheen-color: #ffb094;\"></sl-skeleton>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton';\n\nconst css = `\n  .skeleton-avatars sl-skeleton {\n    display: inline-block;\n    width: 3rem;\n    height: 3rem;\n    margin-right: .5rem;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(1) {\n    --border-radius: 0;\n  }\n\n  .skeleton-avatars sl-skeleton:nth-child(2) {\n    --border-radius: var(--sl-border-radius-medium);\n  }\n`;\n\nconst App = () => <SlSkeleton effect=\"sheen\" style={{ '--color': 'tomato', '--sheen-color': '#ffb094' }} />;\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/spinner.md",
    "content": "---\nmeta:\n  title: Spinner\n  description: Spinners are used to show the progress of an indeterminate operation.\nlayout: component\n---\n\n```html:preview\n<sl-spinner></sl-spinner>\n```\n\n```jsx:react\nimport SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';\n\nconst App = () => <SlSpinner />;\n```\n\n## Examples\n\n### Size\n\nSpinners are sized based on the current font size. To change their size, set the `font-size` property on the spinner itself or on a parent element as shown below.\n\n```html:preview\n<sl-spinner></sl-spinner>\n<sl-spinner style=\"font-size: 2rem;\"></sl-spinner>\n<sl-spinner style=\"font-size: 3rem;\"></sl-spinner>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';\n\nconst App = () => (\n  <>\n    <SlSpinner />\n    <SlSpinner style={{ fontSize: '2rem' }} />\n    <SlSpinner style={{ fontSize: '3rem' }} />\n  </>\n);\n```\n\n{% endraw %}\n\n### Track Width\n\nThe width of the spinner's track can be changed by setting the `--track-width` custom property.\n\n```html:preview\n<sl-spinner style=\"font-size: 50px; --track-width: 10px;\"></sl-spinner>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';\n\nconst App = () => (\n  <SlSpinner\n    style={{\n      fontSize: '3rem',\n      '--track-width': '6px'\n    }}\n  />\n);\n```\n\n{% endraw %}\n\n### Color\n\nThe spinner's colors can be changed by setting the `--indicator-color` and `--track-color` custom properties.\n\n```html:preview\n<sl-spinner style=\"font-size: 3rem; --indicator-color: deeppink; --track-color: pink;\"></sl-spinner>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSpinner from '@shoelace-style/shoelace/dist/react/spinner';\n\nconst App = () => (\n  <SlSpinner\n    style={{\n      fontSize: '3rem',\n      '--indicator-color': 'deeppink',\n      '--track-color': 'pink'\n    }}\n  />\n);\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/split-panel.md",
    "content": "---\nmeta:\n  title: Split Panel\n  description: Split panels display two adjacent panels, allowing the user to reposition them.\nlayout: component\n---\n\n```html:preview\n<sl-split-panel>\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel>\n    <div\n      slot=\"start\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n## Examples\n\n### Initial Position\n\nTo set the initial position, use the `position` attribute. If no position is provided, it will default to 50% of the available space.\n\n```html:preview\n<sl-split-panel position=\"75\">\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n### Initial Position in Pixels\n\nTo set the initial position in pixels instead of a percentage, use the `position-in-pixels` attribute.\n\n```html:preview\n<sl-split-panel position-in-pixels=\"150\">\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel position=\"200\">\n    <div\n      slot=\"start\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n### Vertical\n\nAdd the `vertical` attribute to render the split panel in a vertical orientation where the start and end panels are stacked. You also need to set a height when using the vertical orientation.\n\n```html:preview\n<sl-split-panel vertical style=\"height: 400px;\">\n  <div\n    slot=\"start\"\n    style=\"height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel vertical style={{ height: '400px' }}>\n    <div\n      slot=\"start\"\n      style={{\n        height: '100%',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '100%',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n### Snapping\n\nTo snap panels at specific positions while dragging, you can use the `snap` attribute. You can provide one or more space-separated pixel or percentage values, either as single values or within a `repeat()` expression, which will be repeated along the length of the panel. You can also customize how close the divider must be before snapping with the `snap-threshold` attribute.\n\nFor example, to snap the panel at `100px` and `50%`, use `snap=\"100px 50%\"`.\n\n```html:preview\n<div class=\"split-panel-snapping\">\n  <sl-split-panel snap=\"100px 50%\">\n    <div\n      slot=\"start\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      End\n    </div>\n  </sl-split-panel>\n\n  <div class=\"split-panel-snapping-dots\"></div>\n</div>\n\n<style>\n  .split-panel-snapping {\n    position: relative;\n  }\n\n  .split-panel-snapping-dots::before,\n  .split-panel-snapping-dots::after {\n    content: '';\n    position: absolute;\n    bottom: -12px;\n    width: 6px;\n    height: 6px;\n    border-radius: 50%;\n    background: var(--sl-color-neutral-400);\n    transform: translateX(-3px);\n  }\n\n  .split-panel-snapping .split-panel-snapping-dots::before {\n    left: 100px;\n  }\n\n  .split-panel-snapping .split-panel-snapping-dots::after {\n    left: 50%;\n  }\n</style>\n```\n\nOr, if you want to snap the panel to every `100px` interval, as well as at 50% of the panel's size, you can use `snap=\"repeat(100px) 50%\"`.\n\n```html:preview\n<div class=\"split-panel-snapping-repeat\">\n  <sl-split-panel snap=\"repeat(100px) 50%\">\n    <div\n      slot=\"start\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      End\n    </div>\n  </sl-split-panel>\n</div>\n\n<style>\n  .split-panel-snapping-repeat {\n    position: relative;\n  }\n</style>\n```\n\n### Using a Custom Snap Function\n\nYou can also implement a custom snap function which controls the snapping manually. To do this, you need to acquire a reference to the element in Javascript and set the `snap` property. For example, if you want to snap the divider to either `100px` from the left or `100px` from the right, you can set the `snap` property to a function encoding that logic.\n\n```js\npanel.snap = ({ pos, size }) => (pos < size / 2 ? 100 : size - 100);\n```\n\nNote that the `snap-threshold` property will not automatically be applied if `snap` is set to a function. Instead, the function itself must handle applying the threshold if desired, and is passed a `snapThreshold` member with its parameters.\n\n```html:preview\n<div class=\"split-panel-snapping-fn\">\n  <sl-split-panel>\n    <div\n      slot=\"start\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style=\"height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      End\n    </div>\n  </sl-split-panel>\n\n  <div class=\"split-panel-snapping-dots\"></div>\n</div>\n\n<style>\n  .split-panel-snapping-fn {\n    position: relative;\n  }\n\n  .split-panel-snapping-fn .split-panel-snapping-dots::before,\n  .split-panel-snapping-fn .split-panel-snapping-dots::after {\n    content: '';\n    position: absolute;\n    bottom: -12px;\n    width: 6px;\n    height: 6px;\n    border-radius: 50%;\n    background: var(--sl-color-neutral-400);\n    transform: translateX(-3px);\n  }\n\n  .split-panel-snapping-fn .split-panel-snapping-dots::before {\n    left: 100px;\n  }\n\n  .split-panel-snapping-fn .split-panel-snapping-dots::after {\n    left: calc(100% - 100px);\n  }\n</style>\n\n<script>\n  const container = document.querySelector('.split-panel-snapping-fn');\n  const splitPanel = container.querySelector('sl-split-panel');\n  splitPanel.snap = ({ pos, size }) => (pos < size / 2) ? 100 : (size - 100);\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst css = `\n  .split-panel-snapping {\n    position: relative;\n  }\n\n  .split-panel-snapping-dots::before,\n  .split-panel-snapping-dots::after {\n    content: '';\n    position: absolute;\n    bottom: -12px;\n    width: 6px;\n    height: 6px;\n    border-radius: 50%;\n    background: var(--sl-color-neutral-400);\n    transform: translateX(-3px);\n  }\n\n  .split-panel-snapping-dots::before {\n    left: 100px;\n  }\n\n  .split-panel-snapping-dots::after {\n    left: 50%;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"split-panel-snapping\">\n      <SlSplitPanel snap=\"100px 50%\">\n        <div\n          slot=\"start\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          Start\n        </div>\n        <div\n          slot=\"end\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          End\n        </div>\n      </SlSplitPanel>\n\n      <div className=\"split-panel-snapping-dots\" />\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n{% endraw %}\n\n### Disabled\n\nAdd the `disabled` attribute to prevent the divider from being repositioned.\n\n```html:preview\n<sl-split-panel disabled>\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel disabled>\n    <div\n      slot=\"start\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n### Setting the Primary Panel\n\nBy default, both panels will grow or shrink proportionally when the host element is resized. If a primary panel is designated, it will maintain its size and the secondary panel will grow or shrink to fit the remaining space. You can set the primary panel to `start` or `end` using the `primary` attribute.\n\nTry resizing the example below with each option and notice how the panels respond.\n\n```html:preview\n<div class=\"split-panel-primary\">\n  <sl-split-panel>\n    <div\n      slot=\"start\"\n      style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      End\n    </div>\n  </sl-split-panel>\n\n  <sl-select label=\"Primary Panel\" value=\"\" style=\"max-width: 200px; margin-top: 1rem;\">\n    <sl-option value=\"\">None</sl-option>\n    <sl-option value=\"start\">Start</sl-option>\n    <sl-option value=\"end\">End</sl-option>\n  </sl-select>\n</div>\n\n<script>\n  const container = document.querySelector('.split-panel-primary');\n  const splitPanel = container.querySelector('sl-split-panel');\n  const select = container.querySelector('sl-select');\n\n  select.addEventListener('sl-change', () => (splitPanel.primary = select.value));\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\n\nconst App = () => {\n  const [primary, setPrimary] = useState('');\n\n  return (\n    <>\n      <SlSplitPanel primary={primary}>\n        <div\n          slot=\"start\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          Start\n        </div>\n        <div\n          slot=\"end\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          End\n        </div>\n      </SlSplitPanel>\n\n      <SlSelect\n        label=\"Primary Panel\"\n        value={primary}\n        style={{ maxWidth: '200px', marginTop: '1rem' }}\n        onSlChange={event => setPrimary(event.target.value)}\n      >\n        <SlMenuItem value=\"\">None</SlMenuItem>\n        <SlMenuItem value=\"start\">Start</SlMenuItem>\n        <SlMenuItem value=\"end\">End</SlMenuItem>\n      </SlSelect>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Min & Max\n\nTo set a minimum or maximum size of the primary panel, use the `--min` and `--max` custom properties. Since the secondary panel is flexible, size constraints can only be applied to the primary panel. If no primary panel is designated, these constraints will be applied to the `start` panel.\n\nThis examples demonstrates how you can ensure both panels are at least 150px using `--min`, `--max`, and the `calc()` function.\n\n```html:preview\n<sl-split-panel style=\"--min: 150px; --max: calc(100% - 150px);\">\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel style={{ '--min': '150px', '--max': 'calc(100% - 150px)' }}>\n    <div\n      slot=\"start\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n### Nested Split Panels\n\nCreate complex layouts that can be repositioned independently by nesting split panels.\n\n```html:preview\n<sl-split-panel>\n  <div\n    slot=\"start\"\n    style=\"height: 400px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden\"\n  >\n    Start\n  </div>\n  <div slot=\"end\">\n    <sl-split-panel vertical style=\"height: 400px;\">\n      <div\n        slot=\"start\"\n        style=\"height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden\"\n      >\n        Top\n      </div>\n      <div\n        slot=\"end\"\n        style=\"height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden\"\n      >\n        Bottom\n      </div>\n    </sl-split-panel>\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\n\nconst App = () => (\n  <SlSplitPanel>\n    <div\n      slot=\"start\"\n      style={{\n        height: '400px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div slot=\"end\">\n      <SlSplitPanel vertical style={{ height: '400px' }}>\n        <div\n          slot=\"start\"\n          style={{\n            height: '100%',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          Start\n        </div>\n        <div\n          slot=\"end\"\n          style={{\n            height: '100%',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          End\n        </div>\n      </SlSplitPanel>\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\n### Customizing the Divider\n\nYou can target the `divider` part to apply CSS properties to the divider. To add a custom handle, slot an icon into the `divider` slot. When customizing the divider, make sure to think about focus styles for keyboard users.\n\n```html:preview\n<sl-split-panel style=\"--divider-width: 20px;\">\n  <sl-icon slot=\"divider\" name=\"grip-vertical\"></sl-icon>\n  <div\n    slot=\"start\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    Start\n  </div>\n  <div\n    slot=\"end\"\n    style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n  >\n    End\n  </div>\n</sl-split-panel>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst App = () => (\n  <SlSplitPanel style={{ '--divider-width': '20px' }}>\n    <SlIcon slot=\"divider\" name=\"grip-vertical\" />\n    <div\n      slot=\"start\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style={{\n        height: '200px',\n        background: 'var(--sl-color-neutral-50)',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      End\n    </div>\n  </SlSplitPanel>\n);\n```\n\n{% endraw %}\n\nHere's a more elaborate example that changes the divider's color and width and adds a styled handle.\n\n```html:preview\n<div class=\"split-panel-divider\">\n  <sl-split-panel>\n    <sl-icon slot=\"divider\" name=\"grip-vertical\"></sl-icon>\n    <div\n      slot=\"start\"\n      style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      Start\n    </div>\n    <div\n      slot=\"end\"\n      style=\"height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;\"\n    >\n      End\n    </div>\n  </sl-split-panel>\n</div>\n\n<style>\n  .split-panel-divider sl-split-panel {\n    --divider-width: 2px;\n  }\n\n  .split-panel-divider sl-split-panel::part(divider) {\n    background-color: var(--sl-color-pink-600);\n  }\n\n  .split-panel-divider sl-icon {\n    position: absolute;\n    border-radius: var(--sl-border-radius-small);\n    background: var(--sl-color-pink-600);\n    color: var(--sl-color-neutral-0);\n    padding: 0.5rem 0.125rem;\n  }\n\n  .split-panel-divider sl-split-panel::part(divider):focus-visible {\n    background-color: var(--sl-color-primary-600);\n  }\n\n  .split-panel-divider sl-split-panel:focus-within sl-icon {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n</style>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\n\nconst css = `\n  .split-panel-divider sl-split-panel {\n    --divider-width: 2px;\n  }\n\n  .split-panel-divider sl-split-panel::part(divider) {\n    background-color: var(--sl-color-pink-600);\n  }\n\n  .split-panel-divider sl-icon {\n    position: absolute;\n    border-radius: var(--sl-border-radius-small);\n    background: var(--sl-color-pink-600);\n    color: var(--sl-color-neutral-0);\n    padding: .5rem .125rem;\n  }\n\n  .split-panel-divider sl-split-panel::part(divider):focus-visible {\n    background-color: var(--sl-color-primary-600);\n  }\n\n  .split-panel-divider sl-split-panel:focus-within sl-icon {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"split-panel-divider\">\n      <SlSplitPanel>\n        <SlIcon slot=\"divider\" name=\"grip-vertical\" />\n        <div\n          slot=\"start\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          Start\n        </div>\n        <div\n          slot=\"end\"\n          style={{\n            height: '200px',\n            background: 'var(--sl-color-neutral-50)',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          End\n        </div>\n      </SlSplitPanel>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/switch.md",
    "content": "---\nmeta:\n  title: Switch\n  description: Switches allow the user to toggle an option on or off.\nlayout: component\n---\n\n```html:preview\n<sl-switch>Switch</sl-switch>\n```\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => <SlSwitch>Switch</SlSwitch>;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Checked\n\nUse the `checked` attribute to activate the switch.\n\n```html:preview\n<sl-switch checked>Checked</sl-switch>\n```\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => <SlSwitch checked>Checked</SlSwitch>;\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable the switch.\n\n```html:preview\n<sl-switch disabled>Disabled</sl-switch>\n```\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => <SlSwitch disabled>Disabled</SlSwitch>;\n```\n\n### Sizes\n\nUse the `size` attribute to change a switch's size.\n\n```html:preview\n<sl-switch size=\"small\">Small</sl-switch>\n<br />\n<sl-switch size=\"medium\">Medium</sl-switch>\n<br />\n<sl-switch size=\"large\">Large</sl-switch>\n```\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => (\n  <>\n    <SlSwitch size=\"small\">Small</SlSwitch>\n    <br />\n    <SlSwitch size=\"medium\">Medium</SlSwitch>\n    <br />\n    <SlSwitch size=\"large\">Large</SlSwitch>\n  </>\n);\n```\n\n### Help Text\n\nAdd descriptive help text to a switch with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-switch help-text=\"What should the user know about the switch?\">Label</sl-switch>\n```\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/checkbox';\n\nconst App = () => <SlSwitch help-text=\"What should the user know about the switch?\">Label</SlSwitch>;\n```\n\n### Custom Styles\n\nUse the available custom properties to change how the switch is styled.\n\n```html:preview\n<sl-switch style=\"--width: 80px; --height: 40px; --thumb-size: 36px;\">Really big</sl-switch>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlSwitch from '@shoelace-style/shoelace/dist/react/switch';\n\nconst App = () => (\n  <SlSwitch\n    style={{\n      '--width': '80px',\n      '--height': '32px',\n      '--thumb-size': '26px'\n    }}\n  />\n);\n```\n\n{% endraw %}\n"
  },
  {
    "path": "docs/pages/components/tab-group.md",
    "content": "---\nmeta:\n  title: Tab Group\n  description: Tab groups organize content into a container that shows one section at a time.\nlayout: component\n---\n\nTab groups make use of [tabs](/components/tab) and [tab panels](/components/tab-panel). Each tab must be slotted into the `nav` slot and its `panel` must refer to a tab panel of the same name.\n\n```html:preview\n<sl-tab-group>\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup>\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n## Examples\n\n### Tabs on Bottom\n\nTabs can be shown on the bottom by setting `placement` to `bottom`.\n\n```html:preview\n<sl-tab-group placement=\"bottom\">\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup placement=\"bottom\">\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n### Tabs on Start\n\nTabs can be shown on the starting side by setting `placement` to `start`.\n\n```html:preview\n<sl-tab-group placement=\"start\">\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup placement=\"start\">\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n### Tabs on End\n\nTabs can be shown on the ending side by setting `placement` to `end`.\n\n```html:preview\n<sl-tab-group placement=\"end\">\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup placement=\"end\">\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n### Closable Tabs\n\nAdd the `closable` attribute to a tab to show a close button. This example shows how you can dynamically remove tabs from the DOM when the close button is activated.\n\n```html:preview\n<sl-tab-group class=\"tabs-closable\">\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"closable-1\" closable>Closable 1</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"closable-2\" closable>Closable 2</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"closable-3\" closable>Closable 3</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"closable-1\">This is the first closable tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"closable-2\">This is the second closable tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"closable-3\">This is the third closable tab panel.</sl-tab-panel>\n</sl-tab-group>\n\n<script>\n  const tabGroup = document.querySelector('.tabs-closable');\n\n  tabGroup.addEventListener('sl-close', async event => {\n    const tab = event.target;\n    const panel = tabGroup.querySelector(`sl-tab-panel[name=\"${tab.panel}\"]`);\n\n    // Show the previous tab if the tab is currently active\n    if (tab.active) {\n      tabGroup.show(tab.previousElementSibling.panel);\n    }\n\n    // Remove the tab + panel\n    tab.remove();\n    panel.remove();\n  });\n</script>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => {\n  function handleClose(event) {\n    //\n    // This is a crude example that removes the tab and its panel from the DOM.\n    // There are better ways to manage tab creation/removal in React, but that\n    // would significantly complicate the example.\n    //\n    const tab = event.target;\n    const tabGroup = tab.closest('sl-tab-group');\n    const tabPanel = tabGroup.querySelector(`[aria-labelledby=\"${tab.id}\"]`);\n\n    tab.remove();\n    tabPanel.remove();\n  }\n\n  return (\n    <SlTabGroup className=\"tabs-closable\" onSlClose={handleClose}>\n      <SlTab slot=\"nav\" panel=\"general\">\n        General\n      </SlTab>\n      <SlTab slot=\"nav\" panel=\"closable-1\" closable onSlClose={handleClose}>\n        Closable 1\n      </SlTab>\n      <SlTab slot=\"nav\" panel=\"closable-2\" closable onSlClose={handleClose}>\n        Closable 2\n      </SlTab>\n      <SlTab slot=\"nav\" panel=\"closable-3\" closable onSlClose={handleClose}>\n        Closable 3\n      </SlTab>\n\n      <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n      <SlTabPanel name=\"closable-1\">This is the first closable tab panel.</SlTabPanel>\n      <SlTabPanel name=\"closable-2\">This is the second closable tab panel.</SlTabPanel>\n      <SlTabPanel name=\"closable-3\">This is the third closable tab panel.</SlTabPanel>\n    </SlTabGroup>\n  );\n};\n```\n\n### Scrolling Tabs\n\nWhen there are more tabs than horizontal space allows, the nav will be scrollable.\n\n```html:preview\n<sl-tab-group>\n  <sl-tab slot=\"nav\" panel=\"tab-1\">Tab 1</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-2\">Tab 2</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-3\">Tab 3</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-4\">Tab 4</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-5\">Tab 5</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-6\">Tab 6</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-7\">Tab 7</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-8\">Tab 8</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-9\">Tab 9</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-10\">Tab 10</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-11\">Tab 11</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-12\">Tab 12</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-13\">Tab 13</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-14\">Tab 14</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-15\">Tab 15</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-16\">Tab 16</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-17\">Tab 17</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-18\">Tab 18</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-19\">Tab 19</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-20\">Tab 20</sl-tab>\n\n  <sl-tab-panel name=\"tab-1\">Tab panel 1</sl-tab-panel>\n  <sl-tab-panel name=\"tab-2\">Tab panel 2</sl-tab-panel>\n  <sl-tab-panel name=\"tab-3\">Tab panel 3</sl-tab-panel>\n  <sl-tab-panel name=\"tab-4\">Tab panel 4</sl-tab-panel>\n  <sl-tab-panel name=\"tab-5\">Tab panel 5</sl-tab-panel>\n  <sl-tab-panel name=\"tab-6\">Tab panel 6</sl-tab-panel>\n  <sl-tab-panel name=\"tab-7\">Tab panel 7</sl-tab-panel>\n  <sl-tab-panel name=\"tab-8\">Tab panel 8</sl-tab-panel>\n  <sl-tab-panel name=\"tab-9\">Tab panel 9</sl-tab-panel>\n  <sl-tab-panel name=\"tab-10\">Tab panel 10</sl-tab-panel>\n  <sl-tab-panel name=\"tab-11\">Tab panel 11</sl-tab-panel>\n  <sl-tab-panel name=\"tab-12\">Tab panel 12</sl-tab-panel>\n  <sl-tab-panel name=\"tab-13\">Tab panel 13</sl-tab-panel>\n  <sl-tab-panel name=\"tab-14\">Tab panel 14</sl-tab-panel>\n  <sl-tab-panel name=\"tab-15\">Tab panel 15</sl-tab-panel>\n  <sl-tab-panel name=\"tab-16\">Tab panel 16</sl-tab-panel>\n  <sl-tab-panel name=\"tab-17\">Tab panel 17</sl-tab-panel>\n  <sl-tab-panel name=\"tab-18\">Tab panel 18</sl-tab-panel>\n  <sl-tab-panel name=\"tab-19\">Tab panel 19</sl-tab-panel>\n  <sl-tab-panel name=\"tab-20\">Tab panel 20</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup>\n    <SlTab slot=\"nav\" panel=\"tab-1\">\n      Tab 1\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-2\">\n      Tab 2\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-3\">\n      Tab 3\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-4\">\n      Tab 4\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-5\">\n      Tab 5\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-6\">\n      Tab 6\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-7\">\n      Tab 7\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-8\">\n      Tab 8\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-9\">\n      Tab 9\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-10\">\n      Tab 10\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-11\">\n      Tab 11\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-12\">\n      Tab 12\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-13\">\n      Tab 13\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-14\">\n      Tab 14\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-15\">\n      Tab 15\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-16\">\n      Tab 16\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-17\">\n      Tab 17\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-18\">\n      Tab 18\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-19\">\n      Tab 19\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-20\">\n      Tab 20\n    </SlTab>\n\n    <SlTabPanel name=\"tab-1\">Tab panel 1</SlTabPanel>\n    <SlTabPanel name=\"tab-2\">Tab panel 2</SlTabPanel>\n    <SlTabPanel name=\"tab-3\">Tab panel 3</SlTabPanel>\n    <SlTabPanel name=\"tab-4\">Tab panel 4</SlTabPanel>\n    <SlTabPanel name=\"tab-5\">Tab panel 5</SlTabPanel>\n    <SlTabPanel name=\"tab-6\">Tab panel 6</SlTabPanel>\n    <SlTabPanel name=\"tab-7\">Tab panel 7</SlTabPanel>\n    <SlTabPanel name=\"tab-8\">Tab panel 8</SlTabPanel>\n    <SlTabPanel name=\"tab-9\">Tab panel 9</SlTabPanel>\n    <SlTabPanel name=\"tab-10\">Tab panel 10</SlTabPanel>\n    <SlTabPanel name=\"tab-11\">Tab panel 11</SlTabPanel>\n    <SlTabPanel name=\"tab-12\">Tab panel 12</SlTabPanel>\n    <SlTabPanel name=\"tab-13\">Tab panel 13</SlTabPanel>\n    <SlTabPanel name=\"tab-14\">Tab panel 14</SlTabPanel>\n    <SlTabPanel name=\"tab-15\">Tab panel 15</SlTabPanel>\n    <SlTabPanel name=\"tab-16\">Tab panel 16</SlTabPanel>\n    <SlTabPanel name=\"tab-17\">Tab panel 17</SlTabPanel>\n    <SlTabPanel name=\"tab-18\">Tab panel 18</SlTabPanel>\n    <SlTabPanel name=\"tab-19\">Tab panel 19</SlTabPanel>\n    <SlTabPanel name=\"tab-20\">Tab panel 20</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n### Fixed scroll controls\n\nWhen tabs are scrolled all the way to one side, the scroll button on that side can't be clicked. Set the `fixed-scroll-controls` attribute to keep the effected button visible in that case.\n\n```html:preview\n<sl-tab-group fixed-scroll-controls>\n  <sl-tab slot=\"nav\" panel=\"tab-1\">Tab 1</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-2\">Tab 2</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-3\">Tab 3</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-4\">Tab 4</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-5\">Tab 5</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-6\">Tab 6</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-7\">Tab 7</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-8\">Tab 8</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-9\">Tab 9</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-10\">Tab 10</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-11\">Tab 11</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-12\">Tab 12</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-13\">Tab 13</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-14\">Tab 14</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-15\">Tab 15</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-16\">Tab 16</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-17\">Tab 17</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-18\">Tab 18</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-19\">Tab 19</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"tab-20\">Tab 20</sl-tab>\n\n  <sl-tab-panel name=\"tab-1\">Tab panel 1</sl-tab-panel>\n  <sl-tab-panel name=\"tab-2\">Tab panel 2</sl-tab-panel>\n  <sl-tab-panel name=\"tab-3\">Tab panel 3</sl-tab-panel>\n  <sl-tab-panel name=\"tab-4\">Tab panel 4</sl-tab-panel>\n  <sl-tab-panel name=\"tab-5\">Tab panel 5</sl-tab-panel>\n  <sl-tab-panel name=\"tab-6\">Tab panel 6</sl-tab-panel>\n  <sl-tab-panel name=\"tab-7\">Tab panel 7</sl-tab-panel>\n  <sl-tab-panel name=\"tab-8\">Tab panel 8</sl-tab-panel>\n  <sl-tab-panel name=\"tab-9\">Tab panel 9</sl-tab-panel>\n  <sl-tab-panel name=\"tab-10\">Tab panel 10</sl-tab-panel>\n  <sl-tab-panel name=\"tab-11\">Tab panel 11</sl-tab-panel>\n  <sl-tab-panel name=\"tab-12\">Tab panel 12</sl-tab-panel>\n  <sl-tab-panel name=\"tab-13\">Tab panel 13</sl-tab-panel>\n  <sl-tab-panel name=\"tab-14\">Tab panel 14</sl-tab-panel>\n  <sl-tab-panel name=\"tab-15\">Tab panel 15</sl-tab-panel>\n  <sl-tab-panel name=\"tab-16\">Tab panel 16</sl-tab-panel>\n  <sl-tab-panel name=\"tab-17\">Tab panel 17</sl-tab-panel>\n  <sl-tab-panel name=\"tab-18\">Tab panel 18</sl-tab-panel>\n  <sl-tab-panel name=\"tab-19\">Tab panel 19</sl-tab-panel>\n  <sl-tab-panel name=\"tab-20\">Tab panel 20</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup auto-hide-scroll-buttons>\n    <SlTab slot=\"nav\" panel=\"tab-1\">\n      Tab 1\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-2\">\n      Tab 2\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-3\">\n      Tab 3\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-4\">\n      Tab 4\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-5\">\n      Tab 5\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-6\">\n      Tab 6\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-7\">\n      Tab 7\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-8\">\n      Tab 8\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-9\">\n      Tab 9\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-10\">\n      Tab 10\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-11\">\n      Tab 11\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-12\">\n      Tab 12\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-13\">\n      Tab 13\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-14\">\n      Tab 14\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-15\">\n      Tab 15\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-16\">\n      Tab 16\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-17\">\n      Tab 17\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-18\">\n      Tab 18\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-19\">\n      Tab 19\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"tab-20\">\n      Tab 20\n    </SlTab>\n\n    <SlTabPanel name=\"tab-1\">Tab panel 1</SlTabPanel>\n    <SlTabPanel name=\"tab-2\">Tab panel 2</SlTabPanel>\n    <SlTabPanel name=\"tab-3\">Tab panel 3</SlTabPanel>\n    <SlTabPanel name=\"tab-4\">Tab panel 4</SlTabPanel>\n    <SlTabPanel name=\"tab-5\">Tab panel 5</SlTabPanel>\n    <SlTabPanel name=\"tab-6\">Tab panel 6</SlTabPanel>\n    <SlTabPanel name=\"tab-7\">Tab panel 7</SlTabPanel>\n    <SlTabPanel name=\"tab-8\">Tab panel 8</SlTabPanel>\n    <SlTabPanel name=\"tab-9\">Tab panel 9</SlTabPanel>\n    <SlTabPanel name=\"tab-10\">Tab panel 10</SlTabPanel>\n    <SlTabPanel name=\"tab-11\">Tab panel 11</SlTabPanel>\n    <SlTabPanel name=\"tab-12\">Tab panel 12</SlTabPanel>\n    <SlTabPanel name=\"tab-13\">Tab panel 13</SlTabPanel>\n    <SlTabPanel name=\"tab-14\">Tab panel 14</SlTabPanel>\n    <SlTabPanel name=\"tab-15\">Tab panel 15</SlTabPanel>\n    <SlTabPanel name=\"tab-16\">Tab panel 16</SlTabPanel>\n    <SlTabPanel name=\"tab-17\">Tab panel 17</SlTabPanel>\n    <SlTabPanel name=\"tab-18\">Tab panel 18</SlTabPanel>\n    <SlTabPanel name=\"tab-19\">Tab panel 19</SlTabPanel>\n    <SlTabPanel name=\"tab-20\">Tab panel 20</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n### Manual Activation\n\nWhen focused, keyboard users can press [[Left]] or [[Right]] to select the desired tab. By default, the corresponding tab panel will be shown immediately (automatic activation). You can change this behavior by setting `activation=\"manual\"` which will require the user to press [[Space]] or [[Enter]] before showing the tab panel (manual activation).\n\n```html:preview\n<sl-tab-group activation=\"manual\">\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup activation=\"manual\">\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/tab-panel.md",
    "content": "---\nmeta:\n  title: Tab Panel\n  description: Tab panels are used inside tab groups to display tabbed content.\nlayout: component\n---\n\n```html:preview\n<sl-tab-group>\n  <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"advanced\">Advanced</sl-tab>\n  <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n\n  <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"advanced\">This is the advanced tab panel.</sl-tab-panel>\n  <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n</sl-tab-group>\n```\n\n```jsx:react\nimport SlTab from '@shoelace-style/shoelace/dist/react/tab';\nimport SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';\nimport SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';\n\nconst App = () => (\n  <SlTabGroup>\n    <SlTab slot=\"nav\" panel=\"general\">\n      General\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"custom\">\n      Custom\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"advanced\">\n      Advanced\n    </SlTab>\n    <SlTab slot=\"nav\" panel=\"disabled\" disabled>\n      Disabled\n    </SlTab>\n\n    <SlTabPanel name=\"general\">This is the general tab panel.</SlTabPanel>\n    <SlTabPanel name=\"custom\">This is the custom tab panel.</SlTabPanel>\n    <SlTabPanel name=\"advanced\">This is the advanced tab panel.</SlTabPanel>\n    <SlTabPanel name=\"disabled\">This is a disabled tab panel.</SlTabPanel>\n  </SlTabGroup>\n);\n```\n\n:::tip\nAdditional demonstrations can be found in the [tab group examples](/components/tab-group).\n:::\n"
  },
  {
    "path": "docs/pages/components/tab.md",
    "content": "---\nmeta:\n  title: Tab\n  description: Tabs are used inside tab groups to represent and activate tab panels.\nlayout: component\n---\n\n:::tip\nAdditional demonstrations can be found in the [tab group examples](/components/tab-group).\n:::\n"
  },
  {
    "path": "docs/pages/components/tag.md",
    "content": "---\nmeta:\n  title: Tag\n  description: Tags are used as labels to organize things or to indicate a selection.\nlayout: component\n---\n\n```html:preview\n<sl-tag variant=\"primary\">Primary</sl-tag>\n<sl-tag variant=\"success\">Success</sl-tag>\n<sl-tag variant=\"neutral\">Neutral</sl-tag>\n<sl-tag variant=\"warning\">Warning</sl-tag>\n<sl-tag variant=\"danger\">Danger</sl-tag>\n```\n\n```jsx:react\nimport SlTag from '@shoelace-style/shoelace/dist/react/tag';\n\nconst App = () => (\n  <>\n    <SlTag variant=\"primary\">Primary</SlTag>\n    <SlTag variant=\"success\">Success</SlTag>\n    <SlTag variant=\"neutral\">Neutral</SlTag>\n    <SlTag variant=\"warning\">Warning</SlTag>\n    <SlTag variant=\"danger\">Danger</SlTag>\n  </>\n);\n```\n\n## Examples\n\n### Sizes\n\nUse the `size` attribute to change a tag's size.\n\n```html:preview\n<sl-tag size=\"small\">Small</sl-tag>\n<sl-tag size=\"medium\">Medium</sl-tag>\n<sl-tag size=\"large\">Large</sl-tag>\n```\n\n```jsx:react\nimport SlTag from '@shoelace-style/shoelace/dist/react/tag';\n\nconst App = () => (\n  <>\n    <SlTag size=\"small\">Small</SlTag>\n    <SlTag size=\"medium\">Medium</SlTag>\n    <SlTag size=\"large\">Large</SlTag>\n  </>\n);\n```\n\n### Pill\n\nUse the `pill` attribute to give tags rounded edges.\n\n```html:preview\n<sl-tag size=\"small\" pill>Small</sl-tag>\n<sl-tag size=\"medium\" pill>Medium</sl-tag>\n<sl-tag size=\"large\" pill>Large</sl-tag>\n```\n\n```jsx:react\nimport SlTag from '@shoelace-style/shoelace/dist/react/tag';\n\nconst App = () => (\n  <>\n    <SlTag size=\"small\" pill>\n      Small\n    </SlTag>\n    <SlTag size=\"medium\" pill>\n      Medium\n    </SlTag>\n    <SlTag size=\"large\" pill>\n      Large\n    </SlTag>\n  </>\n);\n```\n\n### Removable\n\nUse the `removable` attribute to add a remove button to the tag.\n\n```html:preview\n<div class=\"tags-removable\">\n  <sl-tag size=\"small\" removable>Small</sl-tag>\n  <sl-tag size=\"medium\" removable>Medium</sl-tag>\n  <sl-tag size=\"large\" removable>Large</sl-tag>\n</div>\n\n<script>\n  const div = document.querySelector('.tags-removable');\n\n  div.addEventListener('sl-remove', event => {\n    const tag = event.target;\n    tag.style.opacity = '0';\n    setTimeout(() => (tag.style.opacity = '1'), 2000);\n  });\n</script>\n\n<style>\n  .tags-removable sl-tag {\n    transition: var(--sl-transition-medium) opacity;\n  }\n</style>\n```\n\n```jsx:react\nimport SlTag from '@shoelace-style/shoelace/dist/react/tag';\n\nconst css = `\n  .tags-removable sl-tag {\n    transition: var(--sl-transition-medium) opacity;\n  }\n`;\n\nconst App = () => {\n  function handleRemove(event) {\n    const tag = event.target;\n    tag.style.opacity = '0';\n    setTimeout(() => (tag.style.opacity = '1'), 2000);\n  }\n\n  return (\n    <>\n      <div className=\"tags-removable\">\n        <SlTag size=\"small\" removable onSlRemove={handleRemove}>\n          Small\n        </SlTag>\n\n        <SlTag size=\"medium\" removable onSlRemove={handleRemove}>\n          Medium\n        </SlTag>\n\n        <SlTag size=\"large\" removable onSlRemove={handleRemove}>\n          Large\n        </SlTag>\n      </div>\n\n      <style>{css}</style>\n    </>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/textarea.md",
    "content": "---\nmeta:\n  title: Textarea\n  description: Textareas collect data from the user and allow multiple lines of text.\nlayout: component\n---\n\n```html:preview\n<sl-textarea></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea />;\n```\n\n:::tip\nThis component works with standard `<form>` elements. Please refer to the section on [form controls](/getting-started/form-controls) to learn more about form submission and client-side validation.\n:::\n\n## Examples\n\n### Labels\n\nUse the `label` attribute to give the textarea an accessible label. For labels that contain HTML, use the `label` slot instead.\n\n```html:preview\n<sl-textarea label=\"Comments\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea label=\"Comments\" />;\n```\n\n### Help Text\n\nAdd descriptive help text to a textarea with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.\n\n```html:preview\n<sl-textarea label=\"Feedback\" help-text=\"Please tell us what you think.\"> </sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea label=\"Feedback\" help-text=\"Please tell us what you think.\" />;\n```\n\n### Rows\n\nUse the `rows` attribute to change the number of text rows that get shown.\n\n```html:preview\n<sl-textarea rows=\"2\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea rows={2} />;\n```\n\n### Placeholders\n\nUse the `placeholder` attribute to add a placeholder.\n\n```html:preview\n<sl-textarea placeholder=\"Type something\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea placeholder=\"Type something\" />;\n```\n\n### Filled Textareas\n\nAdd the `filled` attribute to draw a filled textarea.\n\n```html:preview\n<sl-textarea placeholder=\"Type something\" filled></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea placeholder=\"Type something\" filled />;\n```\n\n### Disabled\n\nUse the `disabled` attribute to disable a textarea.\n\n```html:preview\n<sl-textarea placeholder=\"Textarea\" disabled></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea placeholder=\"Textarea\" disabled />;\n```\n\n### Sizes\n\nUse the `size` attribute to change a textarea's size.\n\n```html:preview\n<sl-textarea placeholder=\"Small\" size=\"small\"></sl-textarea>\n<br />\n<sl-textarea placeholder=\"Medium\" size=\"medium\"></sl-textarea>\n<br />\n<sl-textarea placeholder=\"Large\" size=\"large\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => (\n  <>\n    <SlTextarea placeholder=\"Small\" size=\"small\"></SlTextarea>\n    <br />\n    <SlTextarea placeholder=\"Medium\" size=\"medium\"></SlTextarea>\n    <br />\n    <SlTextarea placeholder=\"Large\" size=\"large\"></SlTextarea>\n  </>\n);\n```\n\n### Prevent Resizing\n\nBy default, textareas can be resized vertically by the user. To prevent resizing, set the `resize` attribute to `none`.\n\n```html:preview\n<sl-textarea resize=\"none\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea resize=\"none\" />;\n```\n\n### Expand with Content\n\nTextareas will automatically resize to expand to fit their content when `resize` is set to `auto`.\n\n```html:preview\n<sl-textarea resize=\"auto\"></sl-textarea>\n```\n\n```jsx:react\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => <SlTextarea resize=\"auto\" />;\n```\n"
  },
  {
    "path": "docs/pages/components/tooltip.md",
    "content": "---\nmeta:\n  title: Tooltip\n  description: Tooltips display additional information based on a specific action.\nlayout: component\n---\n\nA tooltip's target is its _first child element_, so you should only wrap one element inside of the tooltip. If you need the tooltip to show up for multiple elements, nest them inside a container first.\n\nTooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.\n\n```html:preview\n<sl-tooltip content=\"This is a tooltip\">\n  <sl-button>Hover Me</sl-button>\n</sl-tooltip>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <SlTooltip content=\"This is a tooltip\">\n    <SlButton>Hover Me</SlButton>\n  </SlTooltip>\n);\n```\n\n## Examples\n\n### Placement\n\nUse the `placement` attribute to set the preferred placement of the tooltip.\n\n```html:preview\n<div class=\"tooltip-placement-example\">\n  <div class=\"tooltip-placement-example-row\">\n    <sl-tooltip content=\"top-start\" placement=\"top-start\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"top\" placement=\"top\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"top-end\" placement=\"top-end\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n  </div>\n\n  <div class=\"tooltip-placement-example-row\">\n    <sl-tooltip content=\"left-start\" placement=\"left-start\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"right-start\" placement=\"right-start\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n  </div>\n\n  <div class=\"tooltip-placement-example-row\">\n    <sl-tooltip content=\"left\" placement=\"left\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"right\" placement=\"right\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n  </div>\n\n  <div class=\"tooltip-placement-example-row\">\n    <sl-tooltip content=\"left-end\" placement=\"left-end\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"right-end\" placement=\"right-end\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n  </div>\n\n  <div class=\"tooltip-placement-example-row\">\n    <sl-tooltip content=\"bottom-start\" placement=\"bottom-start\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"bottom\" placement=\"bottom\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n\n    <sl-tooltip content=\"bottom-end\" placement=\"bottom-end\">\n      <sl-button></sl-button>\n    </sl-tooltip>\n  </div>\n</div>\n\n<style>\n  .tooltip-placement-example {\n    width: 250px;\n    margin: 1rem;\n  }\n\n  .tooltip-placement-example-row:after {\n    content: '';\n    display: table;\n    clear: both;\n  }\n\n  .tooltip-placement-example sl-button {\n    float: left;\n    width: 2.5rem;\n    margin-right: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n\n  .tooltip-placement-example-row:nth-child(1) sl-tooltip:first-child sl-button,\n  .tooltip-placement-example-row:nth-child(5) sl-tooltip:first-child sl-button {\n    margin-left: calc(40px + 0.25rem);\n  }\n\n  .tooltip-placement-example-row:nth-child(2) sl-tooltip:nth-child(2) sl-button,\n  .tooltip-placement-example-row:nth-child(3) sl-tooltip:nth-child(2) sl-button,\n  .tooltip-placement-example-row:nth-child(4) sl-tooltip:nth-child(2) sl-button {\n    margin-left: calc((40px * 3) + (0.25rem * 3));\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst css = `\n  .tooltip-placement-example {\n    width: 250px;\n  }\n\n  .tooltip-placement-example-row:after {\n    content: '';\n    display: table;\n    clear: both;\n  }\n\n  .tooltip-placement-example sl-button {\n    float: left;\n    width: 2.5rem;\n    margin-right: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n\n  .tooltip-placement-example-row:nth-child(1) sl-tooltip:first-child sl-button,\n  .tooltip-placement-example-row:nth-child(5) sl-tooltip:first-child sl-button {\n    margin-left: calc(40px + 0.25rem);\n  }\n\n  .tooltip-placement-example-row:nth-child(2) sl-tooltip:nth-child(2) sl-button,\n  .tooltip-placement-example-row:nth-child(3) sl-tooltip:nth-child(2) sl-button,\n  .tooltip-placement-example-row:nth-child(4) sl-tooltip:nth-child(2) sl-button {\n    margin-left: calc((40px * 3) + (0.25rem * 3));\n  }\n`;\n\nconst App = () => (\n  <>\n    <div className=\"tooltip-placement-example\">\n      <div className=\"tooltip-placement-example-row\">\n        <SlTooltip content=\"top-start\" placement=\"top-start\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"top\" placement=\"top\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"top-end\" placement=\"top-end\">\n          <SlButton />\n        </SlTooltip>\n      </div>\n\n      <div className=\"tooltip-placement-example-row\">\n        <SlTooltip content=\"left-start\" placement=\"left-start\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"right-start\" placement=\"right-start\">\n          <SlButton />\n        </SlTooltip>\n      </div>\n\n      <div className=\"tooltip-placement-example-row\">\n        <SlTooltip content=\"left\" placement=\"left\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"right\" placement=\"right\">\n          <SlButton />\n        </SlTooltip>\n      </div>\n\n      <div className=\"tooltip-placement-example-row\">\n        <SlTooltip content=\"left-end\" placement=\"left-end\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"right-end\" placement=\"right-end\">\n          <SlButton />\n        </SlTooltip>\n      </div>\n\n      <div className=\"tooltip-placement-example-row\">\n        <SlTooltip content=\"bottom-start\" placement=\"bottom-start\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"bottom\" placement=\"bottom\">\n          <SlButton />\n        </SlTooltip>\n\n        <SlTooltip content=\"bottom-end\" placement=\"bottom-end\">\n          <SlButton />\n        </SlTooltip>\n      </div>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n\n### Click Trigger\n\nSet the `trigger` attribute to `click` to toggle the tooltip on click instead of hover.\n\n```html:preview\n<sl-tooltip content=\"Click again to dismiss\" trigger=\"click\">\n  <sl-button>Click to Toggle</sl-button>\n</sl-tooltip>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <SlTooltip content=\"Click again to dismiss\" trigger=\"click\">\n    <SlButton>Click to Toggle</SlButton>\n  </SlTooltip>\n);\n```\n\n### Manual Trigger\n\nTooltips can be controlled programmatically by setting the `trigger` attribute to `manual`. Use the `open` attribute to control when the tooltip is shown.\n\n```html:preview\n<sl-button style=\"margin-right: 4rem;\">Toggle Manually</sl-button>\n\n<sl-tooltip content=\"This is an avatar\" trigger=\"manual\" class=\"manual-tooltip\">\n  <sl-avatar label=\"User\"></sl-avatar>\n</sl-tooltip>\n\n<script>\n  const tooltip = document.querySelector('.manual-tooltip');\n  const toggle = tooltip.previousElementSibling;\n\n  toggle.addEventListener('click', () => (tooltip.open = !tooltip.open));\n</script>\n```\n\n{% raw %}\n\n```jsx:react\nimport { useState } from 'react';\nimport SlAvatar from '@shoelace-style/shoelace/dist/react/avatar';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <>\n      <SlButton style={{ marginRight: '4rem' }} onClick={() => setOpen(!open)}>\n        Toggle Manually\n      </SlButton>\n\n      <SlTooltip open={open} content=\"This is an avatar\" trigger=\"manual\">\n        <SlAvatar />\n      </SlTooltip>\n    </>\n  );\n};\n```\n\n{% endraw %}\n\n### Removing Arrows\n\nYou can control the size of tooltip arrows by overriding the `--sl-tooltip-arrow-size` design token. To remove them, set the value to `0` as shown below.\n\n```html:preview\n<sl-tooltip content=\"This is a tooltip\" style=\"--sl-tooltip-arrow-size: 0;\">\n  <sl-button>No Arrow</sl-button>\n</sl-tooltip>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <div style={{ '--sl-tooltip-arrow-size': '0' }}>\n    <SlTooltip content=\"This is a tooltip\">\n      <SlButton>Above</SlButton>\n    </SlTooltip>\n\n    <SlTooltip content=\"This is a tooltip\" placement=\"bottom\">\n      <SlButton>Below</SlButton>\n    </SlTooltip>\n  </div>\n);\n```\n\n{% endraw %}\n\nTo override it globally, set it in a root block in your stylesheet after the Shoelace stylesheet is loaded.\n\n```css\n:root {\n  --sl-tooltip-arrow-size: 0;\n}\n```\n\n### HTML in Tooltips\n\nUse the `content` slot to create tooltips with HTML content. Tooltips are designed only for text and presentational elements. Avoid placing interactive content, such as buttons, links, and form controls, in a tooltip.\n\n```html:preview\n<sl-tooltip>\n  <div slot=\"content\">I'm not <strong>just</strong> a tooltip, I'm a <em>tooltip</em> with HTML!</div>\n\n  <sl-button>Hover me</sl-button>\n</sl-tooltip>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <SlTooltip>\n    <div slot=\"content\">\n      I'm not <strong>just</strong> a tooltip, I'm a <em>tooltip</em> with HTML!\n    </div>\n\n    <SlButton>Hover Me</SlButton>\n  </SlTooltip>\n);\n```\n\n### Setting a Maximum Width\n\nUse the `--max-width` custom property to change the width the tooltip can grow to before wrapping occurs.\n\n```html:preview\n<sl-tooltip style=\"--max-width: 80px;\" content=\"This tooltip will wrap after only 80 pixels.\">\n  <sl-button>Hover me</sl-button>\n</sl-tooltip>\n```\n\n{% raw %}\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst App = () => (\n  <SlTooltip style={{ '--max-width': '80px' }} content=\"This tooltip will wrap after only 80 pixels.\">\n    <SlButton>Hover Me</SlButton>\n  </SlTooltip>\n);\n```\n\n{% endraw %}\n\n### Hoisting\n\nTooltips will be clipped if they're inside a container that has `overflow: auto|hidden|scroll`. The `hoist` attribute forces the tooltip to use a fixed positioning strategy, allowing it to break out of the container. In this case, the tooltip will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.\n\n```html:preview\n<div class=\"tooltip-hoist\">\n  <sl-tooltip content=\"This is a tooltip\">\n    <sl-button>No Hoist</sl-button>\n  </sl-tooltip>\n\n  <sl-tooltip content=\"This is a tooltip\" hoist>\n    <sl-button>Hoist</sl-button>\n  </sl-tooltip>\n</div>\n\n<style>\n  .tooltip-hoist {\n    position: relative;\n    border: solid 2px var(--sl-panel-border-color);\n    overflow: hidden;\n    padding: var(--sl-spacing-medium);\n  }\n</style>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlTooltip from '@shoelace-style/shoelace/dist/react/tooltip';\n\nconst css = `\n  .tooltip-hoist {\n    border: solid 2px var(--sl-panel-border-color);\n    overflow: hidden;\n    padding: var(--sl-spacing-medium);\n    position: relative;\n  }\n`;\n\nconst App = () => (\n  <>\n    <div class=\"tooltip-hoist\">\n      <SlTooltip content=\"This is a tooltip\">\n        <SlButton>No Hoist</SlButton>\n      </SlTooltip>\n\n      <SlTooltip content=\"This is a tooltip\" hoist>\n        <SlButton>Hoist</SlButton>\n      </SlTooltip>\n    </div>\n\n    <style>{css}</style>\n  </>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/tree-item.md",
    "content": "---\nmeta:\n  title: Tree Item\n  description: A tree item serves as a hierarchical node that lives inside a tree.\nlayout: component\n---\n\n```html:preview\n<sl-tree>\n  <sl-tree-item>\n    Item 1\n    <sl-tree-item>Item A</sl-tree-item>\n    <sl-tree-item>Item B</sl-tree-item>\n    <sl-tree-item>Item C</sl-tree-item>\n  </sl-tree-item>\n  <sl-tree-item>Item 2</sl-tree-item>\n  <sl-tree-item>Item 3</sl-tree-item>\n</sl-tree>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlTreeItem>\n      Item 1\n      <SlTreeItem>Item A</SlTreeItem>\n      <SlTreeItem>Item B</SlTreeItem>\n      <SlTreeItem>Item C</SlTreeItem>\n    </SlTreeItem>\n    <SlTreeItem>Item 2</SlTreeItem>\n    <SlTreeItem>Item 3</SlTreeItem>\n  </SlTree>\n);\n```\n\n## Examples\n\n### Nested tree items\n\nA tree item can contain other tree items. This allows the node to be expanded or collapsed by the user.\n\n```html:preview\n<sl-tree>\n  <sl-tree-item>\n    Item 1\n    <sl-tree-item>\n      Item A\n      <sl-tree-item>Item Z</sl-tree-item>\n      <sl-tree-item>Item Y</sl-tree-item>\n      <sl-tree-item>Item X</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Item B</sl-tree-item>\n    <sl-tree-item>Item C</sl-tree-item>\n  </sl-tree-item>\n  <sl-tree-item>Item 2</sl-tree-item>\n  <sl-tree-item>Item 3</sl-tree-item>\n</sl-tree>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlTreeItem>\n      Item 1\n      <SlTreeItem>\n        Item A\n        <SlTreeItem>Item Z</SlTreeItem>\n        <SlTreeItem>Item Y</SlTreeItem>\n        <SlTreeItem>Item X</SlTreeItem>\n      </SlTreeItem>\n      <SlTreeItem>Item B</SlTreeItem>\n      <SlTreeItem>Item C</SlTreeItem>\n    </SlTreeItem>\n    <SlTreeItem>Item 2</SlTreeItem>\n    <SlTreeItem>Item 3</SlTreeItem>\n  </SlTree>\n);\n```\n\n### Selected\n\nUse the `selected` attribute to select a tree item initially.\n\n```html:preview\n<sl-tree>\n  <sl-tree-item selected>\n    Item 1\n    <sl-tree-item>Item A</sl-tree-item>\n    <sl-tree-item>Item B</sl-tree-item>\n    <sl-tree-item>Item C</sl-tree-item>\n  </sl-tree-item>\n  <sl-tree-item>Item 2</sl-tree-item>\n  <sl-tree-item>Item 3</sl-tree-item>\n</sl-tree>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlTreeItem selected>\n      Item 1\n      <SlTreeItem>Item A</SlTreeItem>\n      <SlTreeItem>Item B</SlTreeItem>\n      <SlTreeItem>Item C</SlTreeItem>\n    </SlTreeItem>\n    <SlTreeItem>Item 2</SlTreeItem>\n    <SlTreeItem>Item 3</SlTreeItem>\n  </SlTree>\n);\n```\n\n### Expanded\n\nUse the `expanded` attribute to expand a tree item initially.\n\n```html:preview\n<sl-tree>\n  <sl-tree-item expanded>\n    Item 1\n    <sl-tree-item expanded>\n      Item A\n      <sl-tree-item>Item Z</sl-tree-item>\n      <sl-tree-item>Item Y</sl-tree-item>\n      <sl-tree-item>Item X</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Item B</sl-tree-item>\n    <sl-tree-item>Item C</sl-tree-item>\n  </sl-tree-item>\n  <sl-tree-item>Item 2</sl-tree-item>\n  <sl-tree-item>Item 3</sl-tree-item>\n</sl-tree>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlTreeItem expanded>\n      Item 1\n      <SlTreeItem expanded>\n        Item A\n        <SlTreeItem>Item Z</SlTreeItem>\n        <SlTreeItem>Item Y</SlTreeItem>\n        <SlTreeItem>Item X</SlTreeItem>\n      </SlTreeItem>\n      <SlTreeItem>Item B</SlTreeItem>\n      <SlTreeItem>Item C</SlTreeItem>\n    </SlTreeItem>\n    <SlTreeItem>Item 2</SlTreeItem>\n    <SlTreeItem>Item 3</SlTreeItem>\n  </SlTree>\n);\n```\n"
  },
  {
    "path": "docs/pages/components/tree.md",
    "content": "---\nmeta:\n  title: Tree\n  description: Trees allow you to display a hierarchical list of selectable tree items. Items with children can be expanded and collapsed as desired by the user.\nlayout: component\n---\n\n```html:preview\n<sl-tree>\n  <sl-tree-item>\n    Deciduous\n    <sl-tree-item>Birch</sl-tree-item>\n    <sl-tree-item>\n      Maple\n      <sl-tree-item>Field maple</sl-tree-item>\n      <sl-tree-item>Red maple</sl-tree-item>\n      <sl-tree-item>Sugar maple</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Oak</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Coniferous\n    <sl-tree-item>Cedar</sl-tree-item>\n    <sl-tree-item>Pine</sl-tree-item>\n    <sl-tree-item>Spruce</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Non-trees\n    <sl-tree-item>Bamboo</sl-tree-item>\n    <sl-tree-item>Cactus</sl-tree-item>\n    <sl-tree-item>Fern</sl-tree-item>\n  </sl-tree-item>\n</sl-tree>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlTreeItem>\n      Deciduous\n      <SlTreeItem>Birch</SlTreeItem>\n      <SlTreeItem>\n        Maple\n        <SlTreeItem>Field maple</SlTreeItem>\n        <SlTreeItem>Red maple</SlTreeItem>\n        <SlTreeItem>Sugar maple</SlTreeItem>\n      </SlTreeItem>\n      <SlTreeItem>Oak</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Coniferous\n      <SlTreeItem>Cedar</SlTreeItem>\n      <SlTreeItem>Pine</SlTreeItem>\n      <SlTreeItem>Spruce</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Non-trees\n      <SlTreeItem>Bamboo</SlTreeItem>\n      <SlTreeItem>Cactus</SlTreeItem>\n      <SlTreeItem>Fern</SlTreeItem>\n    </SlTreeItem>\n  </SlTree>\n);\n```\n\n## Examples\n\n### Selection Modes\n\nThe `selection` attribute lets you change the selection behavior of the tree.\n\n- Use `single` to allow the selection of a single item (default).\n- Use `multiple` to allow the selection of multiple items.\n- Use `leaf` to only allow leaf nodes to be selected.\n\n```html:preview\n<sl-select id=\"selection-mode\" value=\"single\" label=\"Selection\">\n  <sl-option value=\"single\">Single</sl-option>\n  <sl-option value=\"multiple\">Multiple</sl-option>\n  <sl-option value=\"leaf\">Leaf</sl-option>\n</sl-select>\n\n<br />\n\n<sl-tree class=\"tree-selectable\">\n  <sl-tree-item>\n    Item 1\n    <sl-tree-item>\n      Item A\n      <sl-tree-item>Item Z</sl-tree-item>\n      <sl-tree-item>Item Y</sl-tree-item>\n      <sl-tree-item>Item X</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Item B</sl-tree-item>\n    <sl-tree-item>Item C</sl-tree-item>\n  </sl-tree-item>\n  <sl-tree-item>Item 2</sl-tree-item>\n  <sl-tree-item>Item 3</sl-tree-item>\n</sl-tree>\n\n<script>\n  const selectionMode = document.querySelector('#selection-mode');\n  const tree = document.querySelector('.tree-selectable');\n\n  selectionMode.addEventListener('sl-change', () => {\n    tree.querySelectorAll('sl-tree-item').forEach(item => (item.selected = false));\n    tree.selection = selectionMode.value;\n  });\n</script>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => {\n  const [selection, setSelection] = useState('single');\n\n  return (\n    <>\n      <SlSelect label=\"Selection\" value={selection} onSlChange={event => setSelection(event.target.value)}>\n        <SlMenuItem value=\"single\">single</SlMenuItem>\n        <SlMenuItem value=\"multiple\">multiple</SlMenuItem>\n        <SlMenuItem value=\"leaf\">leaf</SlMenuItem>\n      </SlSelect>\n\n      <br />\n\n      <SlTree selection={selection}>\n        <SlTreeItem>\n          Item 1\n          <SlTreeItem>\n            Item A\n            <SlTreeItem>Item Z</SlTreeItem>\n            <SlTreeItem>Item Y</SlTreeItem>\n            <SlTreeItem>Item X</SlTreeItem>\n          </SlTreeItem>\n          <SlTreeItem>Item B</SlTreeItem>\n          <SlTreeItem>Item C</SlTreeItem>\n        </SlTreeItem>\n        <SlTreeItem>Item 2</SlTreeItem>\n        <SlTreeItem>Item 3</SlTreeItem>\n      </SlTree>\n    </>\n  );\n};\n```\n\n### Showing Indent Guides\n\nIndent guides can be drawn by setting `--indent-guide-width`. You can also change the color, offset, and style, using `--indent-guide-color`, `--indent-guide-style`, and `--indent-guide-offset`, respectively.\n\n```html:preview\n<sl-tree class=\"tree-with-lines\">\n  <sl-tree-item expanded>\n    Deciduous\n    <sl-tree-item>Birch</sl-tree-item>\n    <sl-tree-item expanded>\n      Maple\n      <sl-tree-item>Field maple</sl-tree-item>\n      <sl-tree-item>Red maple</sl-tree-item>\n      <sl-tree-item>Sugar maple</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Oak</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Coniferous\n    <sl-tree-item>Cedar</sl-tree-item>\n    <sl-tree-item>Pine</sl-tree-item>\n    <sl-tree-item>Spruce</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Non-trees\n    <sl-tree-item>Bamboo</sl-tree-item>\n    <sl-tree-item>Cactus</sl-tree-item>\n    <sl-tree-item>Fern</sl-tree-item>\n  </sl-tree-item>\n</sl-tree>\n\n<style>\n  .tree-with-lines {\n    --indent-guide-width: 1px;\n  }\n</style>\n```\n\n{% raw %}\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree class=\"tree-with-lines\" style={{ '--indent-guide-width': '1px' }}>\n    <SlTreeItem expanded>\n      Deciduous\n      <SlTreeItem>Birch</SlTreeItem>\n      <SlTreeItem expanded>\n        Maple\n        <SlTreeItem>Field maple</SlTreeItem>\n        <SlTreeItem>Red maple</SlTreeItem>\n        <SlTreeItem>Sugar maple</SlTreeItem>\n      </SlTreeItem>\n      <SlTreeItem>Oak</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Coniferous\n      <SlTreeItem>Cedar</SlTreeItem>\n      <SlTreeItem>Pine</SlTreeItem>\n      <SlTreeItem>Spruce</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Non-trees\n      <SlTreeItem>Bamboo</SlTreeItem>\n      <SlTreeItem>Cactus</SlTreeItem>\n      <SlTreeItem>Fern</SlTreeItem>\n    </SlTreeItem>\n  </SlTree>\n);\n```\n\n{% endraw %}\n\n### Lazy Loading\n\nUse the `lazy` attribute on a tree item to indicate that the content is not yet present and will be loaded later. When the user tries to expand the node, the `loading` state is set to `true` and the `sl-lazy-load` event will be emitted to allow you to load data asynchronously. The item will remain in a loading state until its content is changed.\n\nIf you want to disable this behavior after the first load, simply remove the `lazy` attribute and, on the next expand, the existing content will be shown instead.\n\n```html:preview\n<sl-tree>\n  <sl-tree-item lazy>Available Trees</sl-tree-item>\n</sl-tree>\n\n<script type=\"module\">\n  const lazyItem = document.querySelector('sl-tree-item[lazy]');\n\n  lazyItem.addEventListener('sl-lazy-load', () => {\n    // Simulate asynchronous loading\n    setTimeout(() => {\n      const subItems = ['Birch', 'Cedar', 'Maple', 'Pine'];\n\n      for (const item of subItems) {\n        const treeItem = document.createElement('sl-tree-item');\n        treeItem.innerText = item;\n        lazyItem.append(treeItem);\n      }\n\n      // Disable lazy mode once the content has been loaded\n      lazyItem.lazy = false;\n    }, 1000);\n  });\n</script>\n```\n\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => {\n  const [childItems, setChildItems] = useState([]);\n  const [lazy, setLazy] = useState(true);\n\n  const handleLazyLoad = () => {\n    // Simulate asynchronous loading\n    setTimeout(() => {\n      setChildItems(['Birch', 'Cedar', 'Maple', 'Pine']);\n\n      // Disable lazy mode once the content has been loaded\n      setLazy(false);\n    }, 1000);\n  };\n\n  return (\n    <SlTree>\n      <SlTreeItem lazy={lazy} onSlLazyLoad={handleLazyLoad}>\n        Available Trees\n        {childItems.map(item => (\n          <SlTreeItem>{item}</SlTreeItem>\n        ))}\n      </SlTreeItem>\n    </SlTree>\n  );\n};\n```\n\n### Customizing the Expand and Collapse Icons\n\nUse the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively. To disable the animation, override the `rotate` property on the `expand-button` part as shown below.\n\n```html:preview\n<sl-tree class=\"custom-icons\">\n  <sl-icon name=\"plus-square\" slot=\"expand-icon\"></sl-icon>\n  <sl-icon name=\"dash-square\" slot=\"collapse-icon\"></sl-icon>\n\n  <sl-tree-item>\n    Deciduous\n    <sl-tree-item>Birch</sl-tree-item>\n    <sl-tree-item>\n      Maple\n      <sl-tree-item>Field maple</sl-tree-item>\n      <sl-tree-item>Red maple</sl-tree-item>\n      <sl-tree-item>Sugar maple</sl-tree-item>\n    </sl-tree-item>\n    <sl-tree-item>Oak</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Coniferous\n    <sl-tree-item>Cedar</sl-tree-item>\n    <sl-tree-item>Pine</sl-tree-item>\n    <sl-tree-item>Spruce</sl-tree-item>\n  </sl-tree-item>\n\n  <sl-tree-item>\n    Non-trees\n    <sl-tree-item>Bamboo</sl-tree-item>\n    <sl-tree-item>Cactus</sl-tree-item>\n    <sl-tree-item>Fern</sl-tree-item>\n  </sl-tree-item>\n</sl-tree>\n\n<style>\n  .custom-icons sl-tree-item::part(expand-button) {\n    /* Disable the expand/collapse animation */\n    rotate: none;\n  }\n</style>\n```\n\n<!-- prettier-ignore -->\n```jsx:react\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => (\n  <SlTree>\n    <SlIcon name=\"plus-square\" slot=\"expand-icon\"></SlIcon>\n    <SlIcon name=\"dash-square\" slot=\"collapse-icon\"></SlIcon>\n\n    <SlTreeItem>\n      Deciduous\n      <SlTreeItem>Birch</SlTreeItem>\n      <SlTreeItem>\n        Maple\n        <SlTreeItem>Field maple</SlTreeItem>\n        <SlTreeItem>Red maple</SlTreeItem>\n        <SlTreeItem>Sugar maple</SlTreeItem>\n      </SlTreeItem>\n      <SlTreeItem>Oak</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Coniferous\n      <SlTreeItem>Cedar</SlTreeItem>\n      <SlTreeItem>Pine</SlTreeItem>\n      <SlTreeItem>Spruce</SlTreeItem>\n    </SlTreeItem>\n\n    <SlTreeItem>\n      Non-trees\n      <SlTreeItem>Bamboo</SlTreeItem>\n      <SlTreeItem>Cactus</SlTreeItem>\n      <SlTreeItem>Fern</SlTreeItem>\n    </SlTreeItem>\n  </SlTree>\n);\n```\n\n### With Icons\n\nDecorative icons can be used before labels to provide hints for each node.\n\n```html:preview\n<sl-tree class=\"tree-with-icons\">\n  <sl-tree-item expanded>\n    <sl-icon name=\"folder\"></sl-icon>\n    Documents\n\n    <sl-tree-item>\n      <sl-icon name=\"folder\"> </sl-icon>\n      Photos\n      <sl-tree-item>\n        <sl-icon name=\"image\"></sl-icon>\n        birds.jpg\n      </sl-tree-item>\n      <sl-tree-item>\n        <sl-icon name=\"image\"></sl-icon>\n        kitten.jpg\n      </sl-tree-item>\n      <sl-tree-item>\n        <sl-icon name=\"image\"></sl-icon>\n        puppy.jpg\n      </sl-tree-item>\n    </sl-tree-item>\n\n    <sl-tree-item>\n      <sl-icon name=\"folder\"></sl-icon>\n      Writing\n      <sl-tree-item>\n        <sl-icon name=\"file\"></sl-icon>\n        draft.txt\n      </sl-tree-item>\n      <sl-tree-item>\n        <sl-icon name=\"file-pdf\"></sl-icon>\n        final.pdf\n      </sl-tree-item>\n      <sl-tree-item>\n        <sl-icon name=\"file-bar-graph\"></sl-icon>\n        sales.xls\n      </sl-tree-item>\n    </sl-tree-item>\n  </sl-tree-item>\n</sl-tree>\n```\n\n```jsx:react\nimport SlIcon from '@shoelace-style/shoelace/dist/react/icon';\nimport SlTree from '@shoelace-style/shoelace/dist/react/tree';\nimport SlTreeItem from '@shoelace-style/shoelace/dist/react/tree-item';\n\nconst App = () => {\n  return (\n    <SlTree class=\"tree-with-icons\">\n      <SlTreeItem expanded>\n        <SlIcon name=\"folder\" />\n        Root\n        <SlTreeItem>\n          <SlIcon name=\"folder\" />\n          Folder 1<SlTreeItem>\n            <SlIcon name=\"files\" />\n            File 1 - 1\n          </SlTreeItem>\n          <SlTreeItem disabled>\n            <SlIcon name=\"files\" />\n            File 1 - 2\n          </SlTreeItem>\n          <SlTreeItem>\n            <SlIcon name=\"files\" />\n            File 1 - 3\n          </SlTreeItem>\n        </SlTreeItem>\n        <SlTreeItem>\n          <SlIcon name=\"files\" />\n          Folder 2<SlTreeItem>\n            <SlIcon name=\"files\" />\n            File 2 - 1\n          </SlTreeItem>\n          <SlTreeItem>\n            <SlIcon name=\"files\" />\n            File 2 - 2\n          </SlTreeItem>\n        </SlTreeItem>\n        <SlTreeItem>\n          <SlIcon name=\"files\" />\n          File 1\n        </SlTreeItem>\n      </SlTreeItem>\n    </SlTree>\n  );\n};\n```\n"
  },
  {
    "path": "docs/pages/components/visually-hidden.md",
    "content": "---\nmeta:\n  title: Visually Hidden\n  description: The visually hidden utility makes content accessible to assistive devices without displaying it on the screen.\nlayout: component\n---\n\nAccording to [The A11Y Project](https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/), \"there are real world situations where visually hiding content may be appropriate, while the content should remain available to assistive technologies, such as screen readers. For instance, hiding a search field's label as a common magnifying glass icon is used in its stead.\"\n\nSince visually hidden content can receive focus when tabbing, the element will become visible when something inside receives focus. This behavior is intentional, as sighted keyboard user won't be able to determine where the focus indicator is without it.\n\n```html:preview\n<div style=\"min-height: 1.875rem;\">\n  <sl-visually-hidden>\n    <a href=\"#\">Skip to main content</a>\n  </sl-visually-hidden>\n</div>\n```\n\n## Examples\n\n### Links That Open in New Windows\n\nIn this example, the link will open a new window. Screen readers will announce \"opens in a new window\" even though the text content isn't visible to sighted users.\n\n```html:preview\n<a href=\"https://example.com/\" target=\"_blank\">\n  Visit External Page\n  <sl-icon name=\"box-arrow-up-right\"></sl-icon>\n  <sl-visually-hidden>opens in a new window</sl-visually-hidden>\n</a>\n```\n\n### Content Conveyed By Context\n\nAdding a label may seem redundant at times, but they're very helpful for unsighted users. Rather than omit them, you can provide context to unsighted users with visually hidden content that will be announced by assistive devices such as screen readers.\n\n```html:preview\n<sl-card style=\"width: 100%; max-width: 360px;\">\n  <header>\n    <sl-visually-hidden>Personal Info</sl-visually-hidden>\n  </header>\n  <sl-input label=\"Name\" style=\"margin-bottom: .5rem;\"></sl-input>\n  <sl-input label=\"Email\" type=\"email\"></sl-input>\n</sl-card>\n```\n"
  },
  {
    "path": "docs/pages/frameworks/angular.md",
    "content": "---\nmeta:\n  title: Angular\n  description: Tips for using Shoelace in your Angular app.\n---\n\n# Angular\n\nAngular [plays nice](https://custom-elements-everywhere.com/#angular) with custom elements, so you can use Shoelace in your Angular apps with ease.\n\n## Installation\n\n### Download the npm package\n\nTo add Shoelace to your Angular app, install the package from npm.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\n### Update the Angular Configuration\n\nNext, [include a theme](/getting-started/themes). In this example, we'll import the light theme.\n\nIts also important to load the components by using a `<script>` tag into the index.html file. However, the Angular way to do it is by adding a script configurations into your angular.json file as follows:\n\n```json\n\"architect\": {\n  \"build\": {\n    ...\n    \"options\": {\n      ...\n      \"styles\": [\n        \"src/styles.scss\",\n        \"@shoelace-style/shoelace/dist/themes/light.css\"\n       ],\n      \"scripts\": [\n        \"@shoelace-style/shoelace/dist/shoelace.js\"\n      ]\n      ...\n```\n\n### Setting up the base path\n\nNext, set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets in the `main.ts`. In this example, we'll use the CDN as a base path.\n\n```jsx\nimport { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';\n\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');\n```\n\n:::tip\nIf you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/%NPMDIR%/assets` into a public folder in your app. Then you can point the base path to that folder instead.\n:::\n\n## Configuration\n\nThen make sure to apply the custom elements schema as shown below.\n\n```js\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [AppComponent],\n  imports: [BrowserModule],\n  providers: [],\n  bootstrap: [AppComponent],\n  schemas: [CUSTOM_ELEMENTS_SCHEMA]\n})\nexport class AppModule {}\n```\n\n## Reference Shoelace components in your Angular component code\n\n```js\nimport { SlDrawer } from '@shoelace-style/shoelace';\n\n@Component({\n  selector: 'app-drawer-example',\n  template: '<div id=\"page\"><button (click)=\"showDrawer()\">Show drawer</button><sl-drawer #drawer label=\"Drawer\" class=\"drawer-focus\" style=\"--size: 50vw\"><p>Drawer content</p></sl-drawer></div>'\n})\nexport class DrawerExampleComponent implements OnInit {\n\n  // use @ViewChild to get a reference to the #drawer element within component template\n  @ViewChild('drawer')\n  drawer?: ElementRef<SlDrawer>;\n\n  ...\n\n  constructor(...) {\n  }\n\n  ngOnInit() {\n  }\n\n  ...\n\n  showDrawer() {\n    // use nativeElement to access Shoelace components\n    this.drawer?.nativeElement.show();\n  }\n}\n```\n\nNow you can start using Shoelace components in your app!\n\n:::tip\nAre you using Shoelace with Angular? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/angular.md)\n:::\n"
  },
  {
    "path": "docs/pages/frameworks/react.md",
    "content": "---\nmeta:\n  title: React\n  description: Tips for using Shoelace in your React app.\n---\n\n# React\n\nShoelace offers a React version of every component to provide an idiomatic experience for React users. You can easily toggle between HTML and React examples throughout the documentation.\n\n## Installation\n\nTo add Shoelace to your React app, install the package from npm.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nNext, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.\n\n```jsx\n// App.jsx\nimport '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';\nimport { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';\n\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');\n```\n\n:::tip\nIf you'd rather not use the CDN for assets, you can create a [build task](https://webpack.js.org/plugins/copy-webpack-plugin/) that copies `node_modules/@shoelace-style/shoelace/%NPMDIR%/assets` into your app's `public` directory. Then you can point the base path to that folder instead.\n:::\n\nNow you can start using components!\n\n### Preact\n\nPreact users facing type errors using components may benefit from setting \"paths\" in their tsconfig.json so that react types will instead resolve to preact/compat as described in [Preact's typescript documentation](https://preactjs.com/guide/v10/typescript/#typescript-preactcompat-configuration).\n\n## Usage\n\n### Importing Components\n\nEvery Shoelace component is available to import as a React component. Note that we're importing the `<SlButton>` _React component_ instead of the `<sl-button>` _custom element_ in the example below.\n\n```jsx\nimport SlButton from '@shoelace-style/shoelace/%NPMDIR%/react/button/index.js';\n\nconst MyComponent = () => <SlButton variant=\"primary\">Click me</SlButton>;\n\nexport default MyComponent;\n```\n\n#### Notes about tree shaking\n\nPreviously, it was recommended to import from a single entrypoint like so:\n\n```jsx\nimport { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';\n```\n\nHowever, tree-shaking extra Shoelace components proved to be a challenge. As a result, we now recommend cherry-picking components you want to use, rather than importing from a single entrypoint.\n\n```diff\n- import { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';\n+ import SlButton from '@shoelace-style/shoelace/%NPMDIR%/react/button/index.js';\n```\n\nYou can find a copy + paste import for each component in the \"importing\" section of its documentation.\n\n### Event Handling\n\nMany Shoelace components emit [custom events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). For example, the [input component](/components/input) emits the `sl-input` event when it receives input. In React, you can listen for the event using `onSlInput`.\n\nHere's how you can bind the input's value to a state variable.\n\n```jsx\nimport { useState } from 'react';\nimport SlInput from '@shoelace-style/shoelace/%NPMDIR%/react/input/index.js';\n\nfunction MyComponent() {\n  const [value, setValue] = useState('');\n\n  return <SlInput value={value} onSlInput={event => setValue(event.target.value)} />;\n}\n\nexport default MyComponent;\n```\n\nIf you're using TypeScript, it's important to note that `event.target` will be a reference to the underlying custom element. You can use `(event.target as any).value` as a quick fix, or you can strongly type the event target as shown below.\n\n```tsx\nimport { useState } from 'react';\nimport SlInput from '@shoelace-style/shoelace/%NPMDIR%/react/input/index.js';\nimport type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input.js';\n\nfunction MyComponent() {\n  const [value, setValue] = useState('');\n\n  return <SlInput value={value} onSlInput={event => setValue((event.target as SlInputElement).value)} />;\n}\n\nexport default MyComponent;\n```\n\nYou can also import the event type for use in your callbacks, shown below.\n\n```tsx\nimport { useCallback, useState } from 'react';\nimport SlInput, { type SlInputEvent } from '@shoelace-style/shoelace/%NPMDIR%/react/input/index.js';\nimport type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input.js';\n\nfunction MyComponent() {\n  const [value, setValue] = useState('');\n  const onInput = useCallback((event: SlInputEvent) => {\n    setValue(event.detail);\n  }, []);\n\n  return <SlInput value={value} onSlInput={event => setValue((event.target as SlInputElement).value)} />;\n}\n\nexport default MyComponent;\n```\n\n## Testing with Jest\n\nTesting with web components can be challenging if your test environment runs in a Node environment (i.e. it doesn't run in a real browser). Fortunately, [Jest](https://jestjs.io/) has made a number of strides to support web components and provide additional browser APIs. However, it's still not a complete replication of a browser environment.\n\nHere are some tips that will help smooth things over if you're having trouble with Jest + Shoelace.\n\n:::tip\nIf you're looking for a fast, modern testing alternative, consider [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/).\n:::\n\n### Upgrade Jest\n\nJest underwent a major revamp and received support for web components in [version 26.5.0](https://github.com/facebook/jest/blob/main/CHANGELOG.md#2650) when it introduced [JSDOM 16.2.0](https://github.com/jsdom/jsdom/blob/master/Changelog.md#1620). This release also included a number of mocks for built-in browser functions such as `MutationObserver`, `document.createRange`, and others.\n\nIf you're using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app), you can update `react-scripts` which will also update Jest.\n\n```\nnpm install react-scripts@latest\n```\n\n### Mock Missing APIs\n\nSome components use `window.matchMedia`, but this function isn't supported by JSDOM so you'll need to mock it yourself.\n\nIn `src/setupTests.js`, add the following.\n\n```js\nObject.defineProperty(window, 'matchMedia', {\n  writable: true,\n  value: jest.fn().mockImplementation(query => ({\n    matches: false,\n    media: query,\n    onchange: null,\n    addListener: jest.fn(), // deprecated\n    removeListener: jest.fn(), // deprecated\n    addEventListener: jest.fn(),\n    removeEventListener: jest.fn(),\n    dispatchEvent: jest.fn()\n  }))\n});\n```\n\nFor more details, refer to Jest's [manual mocking](https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom) documentation.\n\n### Transform ES Modules\n\nES Modules are a [well-supported browser standard](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/). This is how Shoelace is distributed, but most React apps expect CommonJS. As a result, you'll probably run into the following error.\n\n```\nError: Unable to import outside of a module\n```\n\nTo fix this, add the following to your `package.json` which tells the transpiler to process Shoelace modules.\n\n```js\n{\n  \"jest\": {\n    \"transformIgnorePatterns\": [\"node_modules/(?!(@shoelace))\"]\n  }\n}\n```\n\nThese instructions are for apps created via Create React App. If you're using Jest directly, you can add `transformIgnorePatterns` directly into `jest.config.js`.\n\nFor more details, refer to Jest's [`transformIgnorePatterns` customization](https://jestjs.io/docs/tutorial-react-native#transformignorepatterns-customization) documentation.\n\n:::tip\nAre you using Shoelace with React? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/react.md)\n:::\n"
  },
  {
    "path": "docs/pages/frameworks/svelte.md",
    "content": "---\nmeta:\n  title: Svelte\n  description: Tips for using Shoelace in your Svelte app.\n---\n\n# Svelte\n\nSvelte [plays nice](https://custom-elements-everywhere.com/#svelte) with custom elements, so you can use Shoelace in your Svelte apps with ease.\n\n## Installation\n\nTo add Shoelace to your Svelte app, install the package from npm.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nNext, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.\n\n```jsx\n// main.js or main.ts\nimport '@shoelace-style/shoelace/dist/themes/light.css';\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';\n\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');\n```\n\n:::tip\nIf you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.\n:::\n\n## Usage\n\n### QR code generator example\n\n```jsx\n<h1>Live editing</h1>\n\n<sl-input label=\"Message\" value={message} oninput={event => message = event.target.value}></sl-input>\n\n<sl-alert open>\n  <sl-icon slot=\"icon\" name=\"info-circle\"></sl-icon>\n  {message}\n</sl-alert>\n\n<script>\n  import '@shoelace-style/shoelace/dist/components/alert/alert.js'\n  import '@shoelace-style/shoelace/dist/components/input/input.js';\n\n  let message = $state('')\n</script>\n```\n\n### Two-way Binding\n\nOne caveat is there's currently Svelte only supports `bind:value` directive in `<input>`, `<textarea>` and `<select>`, but you can still achieve two-way binding manually.\n\n```jsx\n// ❌ These do not work\n<sl-input bind:value=\"name\"></sl-input>\n\n<sl-select bind:value=\"job\">\n  <sl-option value=\"designer\">Designer</sl-option>\n  <sl-option value=\"developer\">Developer</sl-option>\n</sl-select>\n\n// ✅ These are a bit longer, but work\n<sl-input value={name} oninput={event => name = event.target.value}></sl-input>\n\n<sl-select value={job} onsl-input={event => job = event.target.value}>\n  <sl-option value=\"designer\">Designer</sl-option>\n  <sl-option value=\"developer\">Developer</sl-option>\n</sl-select>\n```\n\n:::tip\nAre you using Shoelace with Svelte? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/svelte.md)\n:::\n\n### Slots\n\nSlots in Shoelace/web components are functionally the same as basic slots in Svelte. Slots can be assigned to elements using the `slot` attribute followed by the name of the slot it is being assigned to.\n\nHere is an example:\n\n```jsx\n<sl-drawer label=\"Drawer\" placement=\"start\" class=\"drawer-placement-start\" bind:open={drawerIsOpen}>\n  This drawer slides in from the start.\n  <div slot=\"footer\">\n    <sl-button variant=\"primary\" onclick={() => (drawerIsOpen = false)}>\n      Close\n    </sl-button>\n  </div>\n</sl-drawer>\n```\n"
  },
  {
    "path": "docs/pages/frameworks/vue-2.md",
    "content": "---\nmeta:\n  title: Vue (version 2)\n  description: Tips for using Shoelace in your Vue 2 app.\n---\n\n# Vue (version 2)\n\nVue [plays nice](https://custom-elements-everywhere.com/#vue) with custom elements, so you can use Shoelace in your Vue apps with ease.\n\n:::tip\nThese instructions are for Vue 2. If you're using Vue 3 or above, please see the [Vue 3 instructions](/frameworks/vue).\n:::\n\n## Installation\n\nTo add Shoelace to your Vue app, install the package from npm.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nNext, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.\n\n```jsx\nimport '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';\nimport { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';\n\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');\n```\n\n:::tip\nIf you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.\n:::\n\n## Configuration\n\nYou'll need to tell Vue to ignore Shoelace components. This is pretty easy because they all start with `sl-`.\n\n```js\nimport Vue from 'vue';\nimport App from './App.vue';\n\nVue.config.ignoredElements = [/sl-/];\n\nconst app = new Vue({\n  render: h => h(App)\n});\n\napp.$mount('#app');\n```\n\nNow you can start using Shoelace components in your app!\n\n## Usage\n\n### Binding Complex Data\n\nWhen binding complex data such as objects and arrays, use the `.prop` modifier to make Vue bind them as a property instead of an attribute.\n\n```html\n<sl-color-picker :swatches.prop=\"mySwatches\" />\n```\n\n### Two-way Binding\n\nOne caveat is there's currently [no support for v-model on custom elements](https://github.com/vuejs/vue/issues/7830), but you can still achieve two-way binding manually.\n\n```html\n<!-- ❌ This doesn't work -->\n<sl-input v-model=\"name\"></sl-input>\n<!-- ✅ This works, but it's a bit longer -->\n<sl-input :value=\"name\" @input=\"name = $event.target.value\"></sl-input>\n```\n\nIf that's too verbose for your liking, you can use a custom directive instead. [This utility](https://www.npmjs.com/package/@shoelace-style/vue-sl-model) adds a custom directive that will work just like `v-model` but for Shoelace components. To install it, use this command.\n\n```bash\nnpm install @shoelace-style/vue-sl-model@1\n```\n\nNext, import the directive and enable it like this.\n\n```js\nimport Vue from 'vue';\nimport ShoelaceModelDirective from '@shoelace-style/vue-sl-model';\nimport App from './App.vue';\n\nVue.use(ShoelaceModelDirective);\nVue.config.ignoredElements = [/sl-/];\n\nconst app = new Vue({\n  render: h => h(App)\n});\n\napp.$mount('#app');\n```\n\nNow you can use the `v-sl-model` directive to keep your data in sync!\n\n```html\n<sl-input v-sl-model=\"name\"></sl-input>\n```\n\n:::tip\nAre you using Shoelace with Vue? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/vue-2.md)\n:::\n"
  },
  {
    "path": "docs/pages/frameworks/vue.md",
    "content": "---\nmeta:\n  title: Vue\n  description: Tips for using Shoelace in your Vue 3 app.\n---\n\n# Vue\n\nVue [plays nice](https://custom-elements-everywhere.com/#vue) with custom elements, so you can use Shoelace in your Vue apps with ease.\n\n:::tip\nThese instructions are for Vue 3 and above. If you're using Vue 2, please see the [Vue 2 instructions](/frameworks/vue-2).\n:::\n\n## Installation\n\nTo add Shoelace to your Vue app, install the package from npm.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nNext, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.\n\n```jsx\n// main.js or main.ts\nimport '@shoelace-style/shoelace/dist/themes/light.css';\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';\n\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');\n```\n\n:::tip\nIf you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.\n:::\n\n## Configuration\n\nIf you haven't configured your Vue.js project to work with custom elements/web components, follow [the instructions here](https://vuejs.org/guide/extras/web-components.html#using-custom-elements-in-vue) based on your project type to ensure your project will not throw an error when it encounters a custom element.\n\nNow you can start using Shoelace components in your app!\n\n## Types\n\nOnce you have configured your application for custom elements, you should be able to use Shoelace in your application without it causing any errors. Unfortunately, this doesn't register the custom elements to behave like components built using Vue. To provide autocomplete information and type safety for your components, you can import the Shoelace Vue types into your `tsconfig.json` to get better integration in your standard Vue and JSX templates.\n\n```json\n{\n  \"compilerOptions\": {\n    \"types\": [\"@shoelace-style/shoelace/dist/types/vue\"]\n  }\n}\n```\n\n## Usage\n\n### QR code generator example\n\n```html\n<template>\n  <div class=\"container\">\n    <h1>QR code generator</h1>\n\n    <sl-input maxlength=\"255\" clearable label=\"Value\" v-model=\"qrCode\"></sl-input>\n\n    <sl-qr-code :value=\"qrCode\"></sl-qr-code>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue';\n  import '@shoelace-style/shoelace/dist/components/qr-code/qr-code.js';\n  import '@shoelace-style/shoelace/dist/components/input/input.js';\n\n  const qrCode = ref();\n</script>\n\n<style>\n  .container {\n    max-width: 400px;\n    margin: 0 auto;\n  }\n\n  sl-input {\n    margin: var(--sl-spacing-large) 0;\n  }\n</style>\n```\n\n### Binding Complex Data\n\nWhen binding complex data such as objects and arrays, use the `.prop` modifier to make Vue bind them as a property instead of an attribute.\n\n```html\n<sl-color-picker :swatches.prop=\"mySwatches\" />\n```\n\n### Two-way Binding\n\nOne caveat is there's currently [no support for v-model on custom elements](https://github.com/vuejs/vue/issues/7830), but you can still achieve two-way binding manually.\n\n```html\n<!-- ❌ This doesn't work -->\n<sl-input v-model=\"name\"></sl-input>\n<!-- ✅ This works, but it's a bit longer -->\n<sl-input :value=\"name\" @input=\"name = $event.target.value\"></sl-input>\n```\n\nIf that's too verbose for your liking, you can use a custom directive instead. [This utility](https://www.npmjs.com/package/@shoelace-style/vue-sl-model) adds a custom directive that will work just like `v-model` but for Shoelace components.\n\n:::tip\nAre you using Shoelace with Vue? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/vue.md)\n:::\n\n### Slots\n\nSlots in Shoelace/web components are functionally the same as basic slots in Vue. Slots can be assigned to elements using the `slot` attribute followed by the name of the slot it is being assigned to.\n\nHere is an example:\n\n```html\n<sl-drawer label=\"Drawer\" placement=\"start\" class=\"drawer-placement-start\" :open=\"drawerIsOpen\">\n  This drawer slides in from the start.\n  <div slot=\"footer\">\n    <sl-button variant=\"primary\" @click=\" drawerIsOpen = false\">Close</sl-button>\n  </div>\n</sl-drawer>\n```\n"
  },
  {
    "path": "docs/pages/getting-started/customizing.md",
    "content": "---\nmeta:\n  title: Customizing\n  description: Learn how to customize Shoelace through parts and custom properties.\n---\n\n# Customizing\n\nShoelace components can be customized at a high level through design tokens. This gives you control over theme colors and general styling. For more advanced customizations, you can make use of CSS parts and custom properties to target individual components.\n\n## Design Tokens\n\nShoelace makes use of several design tokens to provide a consistent appearance across components. You can customize them and use them in your own application with pure CSS — no preprocessor required.\n\nDesign tokens offer a high-level way to customize the library with minimal effort. There are no component-specific variables, however, as design tokens are intended to be generic and highly reusable. To customize an individual component, refer to the section entitled [CSS Parts](#css-parts).\n\nDesign tokens are accessed through CSS custom properties that are defined in your theme. Because design tokens live at the page level, they're prefixed with `--sl-` to avoid collisions with other libraries.\n\nTo customize a design token, simply override it in your stylesheet using a `:root` block. Here's an example that changes the primary theme to purple based on existing [color primitives](/tokens/color#primitives).\n\n```css\n:root {\n  /* Changes the primary theme color to purple using primitives */\n  --sl-color-primary-50: var(--sl-color-purple-50);\n  --sl-color-primary-100: var(--sl-color-purple-100);\n  --sl-color-primary-200: var(--sl-color-purple-200);\n  --sl-color-primary-300: var(--sl-color-purple-300);\n  --sl-color-primary-400: var(--sl-color-purple-400);\n  --sl-color-primary-500: var(--sl-color-purple-500);\n  --sl-color-primary-600: var(--sl-color-purple-600);\n  --sl-color-primary-700: var(--sl-color-purple-700);\n  --sl-color-primary-800: var(--sl-color-purple-800);\n  --sl-color-primary-900: var(--sl-color-purple-900);\n  --sl-color-primary-950: var(--sl-color-purple-950);\n}\n```\n\nMany design tokens are described further along in this documentation. For a complete list, refer to `src/themes/light.css` in the project's [source code](https://github.com/shoelace-style/shoelace/blob/current/src/themes/light.css).\n\n## CSS Parts\n\nWhereas design tokens offer a high-level way to customize the library, CSS parts offer a low-level way to customize individual components. Again, this is done with pure CSS — no preprocessor required.\n\nShoelace components use a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) to encapsulate their styles and behaviors. As a result, you can't simply target their internals with the usual CSS selectors. Instead, components expose \"parts\" that can be targeted with the [CSS part selector](https://developer.mozilla.org/en-US/docs/Web/CSS/::part), or `::part()`.\n\nHere's an example that modifies buttons with the `tomato-button` class.\n\n```html:preview\n<sl-button class=\"tomato-button\"> Tomato Button </sl-button>\n\n<style>\n  .tomato-button::part(base) {\n    background: var(--sl-color-neutral-0);\n    border: solid 1px tomato;\n  }\n\n  .tomato-button::part(base):hover {\n    background: rgba(255, 99, 71, 0.1);\n  }\n\n  .tomato-button::part(base):active {\n    background: rgba(255, 99, 71, 0.2);\n  }\n\n  .tomato-button::part(base):focus-visible {\n    box-shadow: 0 0 0 3px rgba(255, 99, 71, 0.33);\n  }\n\n  .tomato-button::part(label) {\n    color: tomato;\n  }\n</style>\n```\n\nAt first glance, this approach might seem a bit verbose or even limiting, but it comes with a few important advantages:\n\n- Customizations can be made to components with explicit selectors, such as `::part(icon)`, rather than implicit selectors, such as `.button > div > span + .icon`, that are much more fragile.\n\n- The internal structure of a component will likely change as it evolves. By exposing CSS parts through an API, the internals can be reworked without fear of breaking customizations as long as its parts remain intact.\n\n- It encourages us to think more about how components are designed and how customizations should be allowed before users can take advantage of them. Once we opt a part into the component's API, it's guaranteed to be supported and can't be removed until a major version of the library is released.\n\nMost (but not all) components expose parts. You can find them in each component's API documentation under the \"CSS Parts\" section.\n\n## Custom Properties\n\nFor convenience, some components expose CSS custom properties you can override. These are not design tokens, nor do they have the same `--sl-` prefix since they're scoped to a component.\n\nYou can set custom properties on a component in your stylesheet.\n\n```css\nsl-avatar {\n  --size: 6rem;\n}\n```\n\nThis will also work if you need to target a subset of components with a specific class.\n\n```css\nsl-avatar.your-class {\n  --size: 6rem;\n}\n```\n\nAlternatively, you can set them inline directly on the element.\n\n```html\n<sl-avatar style=\"--size: 6rem;\"></sl-avatar>\n```\n\nNot all components expose CSS custom properties. For those that do, they can be found in the component's API documentation.\n\n## Animations\n\nSome components use animation, such as when a dialog is shown or hidden. Animations are performed using the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) rather than CSS. However, you can still customize them through Shoelace's animation registry. If a component has customizable animations, they'll be listed in the \"Animation\" section of its documentation.\n\nTo customize a default animation, use the `setDefaultAnimation()` method. The function accepts an animation name (found in the component's docs) and an object with `keyframes`, and `options` or `null` to disable the animation.\n\nThis example will make all dialogs use a custom show animation.\n\n```js\nimport { setDefaultAnimation } from '@shoelace-style/shoelace/dist/utilities/animation-registry.js';\n\n// Change the default animation for all dialogs\nsetDefaultAnimation('dialog.show', {\n  keyframes: [\n    { transform: 'rotate(-10deg) scale(0.5)', opacity: '0' },\n    { transform: 'rotate(0deg) scale(1)', opacity: '1' }\n  ],\n  options: {\n    duration: 500\n  }\n});\n```\n\n:::tip\nTo support RTL languages in your animation, you can pass an additional property called `rtlKeyframes`. This property shares the same type as `keyframes` and will be automatically used when the component's directionality is RTL. If `rtlKeyframes` is not provided, `keyframes` will be used as a fallback.\n:::\n\nIf you only want to target a single component, use the `setAnimation()` method instead. This function accepts an element, an animation name, and an object comprised of animation `keyframes` and `options`.\n\nIn this example, only the target dialog will use a custom show animation.\n\n```js\nimport { setAnimation } from '@shoelace-style/shoelace/dist/utilities/animation-registry.js';\n\n// Change the animation for a single dialog\nconst dialog = document.querySelector('#my-dialog');\n\nsetAnimation(dialog, 'dialog.show', {\n  keyframes: [\n    { transform: 'rotate(-10deg) scale(0.5)', opacity: '0' },\n    { transform: 'rotate(0deg) scale(1)', opacity: '1' }\n  ],\n  options: {\n    duration: 500\n  }\n});\n```\n\nTo learn more about creating Web Animations, refer to the documentation for [`Element.animate()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate).\n\n:::tip\nAnimations respect the users `prefers-reduced-motion` setting. When this setting is enabled, animations will not be played. To disable animations for all users, pass in `null` instead of a keyframes/options object.\n:::\n"
  },
  {
    "path": "docs/pages/getting-started/form-controls.md",
    "content": "---\nmeta:\n  title: Form Controls\n  description: Some things to note about Shoelace and forms.\n---\n\n# Form Controls\n\nEvery Shoelace component makes use of a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) to encapsulate markup, styles, and behavior. One caveat of this approach is that native `<form>` elements do not recognize form controls located inside a shadow root.\n\nShoelace solves this problem by using the [`formdata`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/formdata_event) event, which is [available in all modern browsers](https://caniuse.com/mdn-api_htmlformelement_formdata_event). This means, when a form is submitted, Shoelace form controls will automatically append their values to the `FormData` object that's used to submit the form. In most cases, things will \"just work.\" However, if you're using a form serialization library, it might need to be adapted to recognize Shoelace form controls.\n\n:::tip\nShoelace uses event listeners to intercept the form's `formdata` and `submit` events. This allows it to inject data and trigger validation as necessary. If you're also attaching an event listener to the form, _you must attach it after Shoelace form controls are connected to the DOM_, otherwise your logic will run before Shoelace has a chance to inject form data and validate form controls.\n:::\n\n## Data Serialization\n\nSerialization is just a fancy word for collecting form data. If you're relying on standard form submissions, e.g. `<form action=\"...\">`, you can probably skip this section. However, most modern apps use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or a library such as [axios](https://github.com/axios/axios) to submit forms using JavaScript.\n\nThe [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) interface offers a standard way to serialize forms in the browser. You can create a `FormData` object from any `<form>` element like this.\n\n```js\nconst form = document.querySelector('form');\nconst data = new FormData(form);\n\n// All form control data is available in a FormData object\n```\n\nHowever, some folks find `FormData` tricky to work with or they need to pass a JSON payload to their server. To accommodate this, Shoelace offers a serialization utility that gathers form data and returns a simple JavaScript object instead.\n\n```js\nimport { serialize } from '@shoelace-style/shoelace/dist/utilities/form.js';\n\nconst form = document.querySelector('form');\nconst data = serialize(form);\n\n// All form control data is available in a plain object\n```\n\nThis results in an object with name/value pairs that map to each form control. If more than one form control shares the same name, the values will be passed as an array, e.g. `{ name: ['value1', 'value2'] }`.\n\n## Constraint Validation\n\nClient-side validation can be enabled through the browser's [Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) for Shoelace form controls. You can activate it using attributes such as `required`, `pattern`, `minlength`, `maxlength`, etc. Shoelace implements many of the same attributes as native form controls, but check the documentation for a list of supported properties for each component.\n\nIf you don't want to use client-side validation, you can suppress this behavior by adding `novalidate` to the surrounding `<form>` element.\n\n:::tip\nIf this syntax looks unfamiliar, don't worry! Most of what you're learning on this page is platform knowledge that applies to regular form controls, too.\n:::\n\n:::warning\nClient-side validation can be used to improve the UX of forms, but it is not a replacement for server-side validation. **You should always validate and sanitize user input on the server!**\n:::\n\n### Required Fields\n\nTo make a field required, use the `required` attribute. Required fields will automatically receive a `*` after their labels. This is configurable through the `--sl-input-required-content` custom property.\n\nThe form will not be submitted if a required field is incomplete.\n\n```html:preview\n<form class=\"input-validation-required\">\n  <sl-input name=\"name\" label=\"Name\" required></sl-input>\n  <br />\n  <sl-select label=\"Favorite Animal\" clearable required>\n    <sl-option value=\"birds\">Birds</sl-option>\n    <sl-option value=\"cats\">Cats</sl-option>\n    <sl-option value=\"dogs\">Dogs</sl-option>\n    <sl-option value=\"other\">Other</sl-option>\n  </sl-select>\n  <br />\n  <sl-textarea name=\"comment\" label=\"Comment\" required></sl-textarea>\n  <br />\n  <sl-checkbox required>Check me before submitting</sl-checkbox>\n  <br /><br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.input-validation-required');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-checkbox'),\n    customElements.whenDefined('sl-input'),\n    customElements.whenDefined('sl-option'),\n    customElements.whenDefined('sl-select'),\n    customElements.whenDefined('sl-textarea')\n  ]).then(() => {\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      alert('All fields are valid!');\n    });\n  });\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\nimport SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';\nimport SlSelect from '@shoelace-style/shoelace/dist/react/select';\nimport SlTextarea from '@shoelace-style/shoelace/dist/react/textarea';\n\nconst App = () => {\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <SlInput name=\"name\" label=\"Name\" required />\n      <br />\n      <SlSelect label=\"Favorite Animal\" clearable required>\n        <SlMenuItem value=\"birds\">Birds</SlMenuItem>\n        <SlMenuItem value=\"cats\">Cats</SlMenuItem>\n        <SlMenuItem value=\"dogs\">Dogs</SlMenuItem>\n        <SlMenuItem value=\"other\">Other</SlMenuItem>\n      </SlSelect>\n      <br />\n      <SlTextarea name=\"comment\" label=\"Comment\" required></SlTextarea>\n      <br />\n      <SlCheckbox required>Check me before submitting</SlCheckbox>\n      <br />\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n### Input Patterns\n\nTo restrict a value to a specific [pattern](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern), use the `pattern` attribute. This example only allows the letters A-Z, so the form will not submit if a number or symbol is entered. This only works with `<sl-input>` elements.\n\n```html:preview\n<form class=\"input-validation-pattern\">\n  <sl-input name=\"letters\" required label=\"Letters\" pattern=\"[A-Za-z]+\"></sl-input>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n  <sl-button type=\"reset\" variant=\"default\">Reset</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.input-validation-pattern');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-input')\n  ]).then(() => {\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      alert('All fields are valid!');\n    });\n  });\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <SlInput name=\"letters\" required label=\"Letters\" pattern=\"[A-Za-z]+\" />\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n### Input Types\n\nSome input types will automatically trigger constraints, such as `email` and `url`.\n\n```html:preview\n<form class=\"input-validation-type\">\n  <sl-input type=\"email\" label=\"Email\" placeholder=\"you@example.com\" required></sl-input>\n  <br />\n  <sl-input type=\"url\" label=\"URL\" placeholder=\"https://example.com/\" required></sl-input>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n  <sl-button type=\"reset\" variant=\"default\">Reset</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.input-validation-type');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-input')\n  ]).then(() => {\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      alert('All fields are valid!');\n    });\n  });\n</script>\n```\n\n```jsx:react\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <SlInput type=\"email\" label=\"Email\" placeholder=\"you@example.com\" required />\n      <br />\n      <SlInput type=\"url\" label=\"URL\" placeholder=\"https://example.com/\" required />\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n### Custom Error Messages\n\nTo create a custom validation error, pass a non-empty string to the `setCustomValidity()` method. This will override any existing validation constraints. The form will not be submitted when a custom validity is set and the browser will show a validation error when the containing form is submitted. To make the input valid again, call `setCustomValidity()` again with an empty string.\n\n```html:preview\n<form class=\"input-validation-custom\">\n  <sl-input label=\"Type “shoelace”\" required></sl-input>\n  <br />\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n  <sl-button type=\"reset\" variant=\"default\">Reset</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.input-validation-custom');\n  const input = form.querySelector('sl-input');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-input')\n  ]).then(() => {\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      alert('All fields are valid!');\n    });\n\n    input.addEventListener('sl-input', () => {\n      if (input.value === 'shoelace') {\n        input.setCustomValidity('');\n      } else {\n        input.setCustomValidity(\"Hey, you're supposed to type 'shoelace' before submitting this!\");\n      }\n    });\n  });\n</script>\n```\n\n```jsx:react\nimport { useRef, useState } from 'react';\nimport SlButton from '@shoelace-style/shoelace/dist/react/button';\nimport SlInput from '@shoelace-style/shoelace/dist/react/input';\n\nconst App = () => {\n  const input = useRef(null);\n  const [value, setValue] = useState('');\n\n  function handleInput(event) {\n    setValue(event.target.value);\n\n    if (event.target.value === 'shoelace') {\n      input.current.setCustomValidity('');\n    } else {\n      input.current.setCustomValidity(\"Hey, you're supposed to type 'shoelace' before submitting this!\");\n    }\n  }\n\n  function handleSubmit(event) {\n    event.preventDefault();\n    alert('All fields are valid!');\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <SlInput ref={input} label=\"Type 'shoelace'\" required value={value} onSlInput={handleInput} />\n      <br />\n      <SlButton type=\"submit\" variant=\"primary\">\n        Submit\n      </SlButton>\n    </form>\n  );\n};\n```\n\n:::tip\nCustom validation can be applied to any form control that supports the `setCustomValidity()` method. It is not limited to inputs and textareas.\n:::\n\n## Custom Validation Styles\n\nDue to the many ways form controls are used, Shoelace doesn't provide out of the box validation styles for form controls as part of its default theme. Instead, the following attributes will be applied to reflect a control's validity as users interact with it. You can use them to create custom styles for any of the validation states you're interested in.\n\n- `data-required` - the form control is required\n- `data-optional` - the form control is optional\n- `data-invalid` - the form control is currently invalid\n- `data-valid` - the form control is currently valid\n- `data-user-invalid` - the form control is currently invalid and the user has interacted with it\n- `data-user-valid` - the form control is currently valid and the user has interacted with it\n\nThese attributes map to the browser's built-in pseudo classes for validation: [`:required`](https://developer.mozilla.org/en-US/docs/Web/CSS/:required), [`:optional`](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional), [`:invalid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:invalid), [`:valid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:valid), and the proposed [`:user-invalid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid) and [`:user-valid`](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-valid).\n\n:::tip\nIn the future, data attributes will be replaced with custom pseudo classes such as `:--valid` and `:--invalid`. Shoelace is using data attributes as a workaround until browsers support custom states through [`ElementInternals.states`](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states).\n:::\n\n### Styling Invalid Form Controls\n\nYou can target validity using any of the aforementioned data attributes, but it's usually preferable to target `data-user-invalid` and `data-user-valid` since they get applied only after a user interaction such as typing or submitting. This prevents empty form controls from appearing invalid immediately, which often results in a poor user experience.\n\nThis example demonstrates custom validation styles using `data-user-invalid` and `data-user-valid`. Try Typing in the fields to see how validity changes with user input.\n\n```html:preview\n<form class=\"validity-styles\">\n  <sl-input\n    name=\"name\"\n    label=\"Name\"\n    help-text=\"What would you like people to call you?\"\n    autocomplete=\"off\"\n    required\n  ></sl-input>\n\n  <sl-select name=\"animal\" label=\"Favorite Animal\" help-text=\"Select the best option.\" clearable required>\n    <sl-option value=\"birds\">Birds</sl-option>\n    <sl-option value=\"cats\">Cats</sl-option>\n    <sl-option value=\"dogs\">Dogs</sl-option>\n    <sl-option value=\"other\">Other</sl-option>\n  </sl-select>\n\n  <sl-checkbox value=\"accept\" required>Accept terms and conditions</sl-checkbox>\n\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n  <sl-button type=\"reset\" variant=\"default\">Reset</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.validity-styles');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-checkbox'),\n    customElements.whenDefined('sl-input'),\n    customElements.whenDefined('sl-option'),\n    customElements.whenDefined('sl-select')\n  ]).then(() => {\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      alert('All fields are valid!');\n    });\n  });\n</script>\n\n<style>\n  .validity-styles sl-input,\n  .validity-styles sl-select,\n  .validity-styles sl-checkbox {\n    display: block;\n    margin-bottom: var(--sl-spacing-medium);\n  }\n\n  /* user invalid styles */\n  .validity-styles sl-input[data-user-invalid]::part(base),\n  .validity-styles sl-select[data-user-invalid]::part(combobox),\n  .validity-styles sl-checkbox[data-user-invalid]::part(control) {\n    border-color: var(--sl-color-danger-600);\n  }\n\n  .validity-styles [data-user-invalid]::part(form-control-label),\n  .validity-styles [data-user-invalid]::part(form-control-help-text),\n  .validity-styles sl-checkbox[data-user-invalid]::part(label) {\n    color: var(--sl-color-danger-700);\n  }\n\n  .validity-styles sl-checkbox[data-user-invalid]::part(control) {\n    outline: none;\n  }\n\n  .validity-styles sl-input:focus-within[data-user-invalid]::part(base),\n  .validity-styles sl-select:focus-within[data-user-invalid]::part(combobox),\n  .validity-styles sl-checkbox:focus-within[data-user-invalid]::part(control) {\n    border-color: var(--sl-color-danger-600);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);\n  }\n\n  /* User valid styles */\n  .validity-styles sl-input[data-user-valid]::part(base),\n  .validity-styles sl-select[data-user-valid]::part(combobox),\n  .validity-styles sl-checkbox[data-user-valid]::part(control) {\n    border-color: var(--sl-color-success-600);\n  }\n\n  .validity-styles [data-user-valid]::part(form-control-label),\n  .validity-styles [data-user-valid]::part(form-control-help-text),\n  .validity-styles sl-checkbox[data-user-valid]::part(label) {\n    color: var(--sl-color-success-700);\n  }\n\n  .validity-styles sl-checkbox[data-user-valid]::part(control) {\n    background-color: var(--sl-color-success-600);\n    outline: none;\n  }\n\n  .validity-styles sl-input:focus-within[data-user-valid]::part(base),\n  .validity-styles sl-select:focus-within[data-user-valid]::part(combobox),\n  .validity-styles sl-checkbox:focus-within[data-user-valid]::part(control) {\n    border-color: var(--sl-color-success-600);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-success-300);\n  }\n</style>\n```\n\n## Inline Form Validation\n\nBy default, Shoelace form controls use the browser's tooltip-style error messages. No mechanism is provided to show errors inline, as there are too many opinions on how that would work when combined with native form controls and other custom elements. You can, however, implement your own solution using the following technique.\n\nTo disable the browser's error messages, you need to cancel the `sl-invalid` event. Then you can apply your own inline validation errors. This example demonstrates a primitive way to do this.\n\n```html:preview\n<form class=\"inline-validation\">\n  <sl-input\n    name=\"name\"\n    label=\"Name\"\n    help-text=\"What would you like people to call you?\"\n    autocomplete=\"off\"\n    required\n  ></sl-input>\n\n  <div id=\"name-error\" aria-live=\"polite\" hidden></div>\n\n  <sl-button type=\"submit\" variant=\"primary\">Submit</sl-button>\n  <sl-button type=\"reset\" variant=\"default\">Reset</sl-button>\n</form>\n\n<script type=\"module\">\n  const form = document.querySelector('.inline-validation');\n  const nameError = document.querySelector('#name-error');\n\n  // Wait for controls to be defined before attaching form listeners\n  await Promise.all([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-input')\n  ]).then(() => {\n    // A form control is invalid\n    form.addEventListener(\n      'sl-invalid',\n      event => {\n        // Suppress the browser's constraint validation message\n        event.preventDefault();\n\n        nameError.textContent = `Error: ${event.target.validationMessage}`;\n        nameError.hidden = false;\n\n        event.target.focus();\n      },\n      { capture: true } // you must use capture since sl-invalid doesn't bubble!\n    );\n\n    // Handle form submit\n    form.addEventListener('submit', event => {\n      event.preventDefault();\n      nameError.hidden = true;\n      nameError.textContent = '';\n      setTimeout(() => alert('All fields are valid'), 50);\n    });\n\n    // Handle form reset\n    form.addEventListener('reset', event => {\n      nameError.hidden = true;\n      nameError.textContent = '';\n    });\n  });\n</script>\n\n<style>\n  #name-error {\n    font-size: var(--sl-input-help-text-font-size-medium);\n    color: var(--sl-color-danger-700);\n  }\n\n  #name-error ~ sl-button {\n    margin-top: var(--sl-spacing-medium);\n  }\n\n  .inline-validation sl-input {\n    display: block;\n  }\n\n  /* user invalid styles */\n  .inline-validation sl-input[data-user-invalid]::part(base) {\n    border-color: var(--sl-color-danger-600);\n  }\n\n  .inline-validation [data-user-invalid]::part(form-control-label),\n  .inline-validation [data-user-invalid]::part(form-control-help-text) {\n    color: var(--sl-color-danger-700);\n  }\n\n  .inline-validation sl-input:focus-within[data-user-invalid]::part(base) {\n    border-color: var(--sl-color-danger-600);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);\n  }\n\n  /* User valid styles */\n  .inline-validation sl-input[data-user-valid]::part(base) {\n    border-color: var(--sl-color-success-600);\n  }\n\n  .inline-validation [data-user-valid]::part(form-control-label),\n  .inline-validation [data-user-valid]::part(form-control-help-text) {\n    color: var(--sl-color-success-700);\n  }\n\n  .inline-validation sl-input:focus-within[data-user-valid]::part(base) {\n    border-color: var(--sl-color-success-600);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-success-300);\n  }\n</style>\n```\n\n:::warning\nThis example is meant to demonstrate the concept of providing your own error messages inline. It is not intended to scale to more complex forms. Users who want this functionality are encouraged to build a more appropriate validation solution using the techniques shown below. Depending on how you implement this feature, custom error messages may affect the accessibility of your form controls.\n:::\n\n## Getting Associated Form Controls\n\nAt this time, using [`HTMLFormElement.elements`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements) will not return Shoelace form controls because the browser is unaware of their status as custom element form controls. Fortunately, Shoelace provides an `elements()` function that does something very similar. However, instead of returning an [`HTMLFormControlsCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormControlsCollection), it returns an array of HTML and Shoelace form controls in the order they appear in the DOM.\n\n```js\nimport { getFormControls } from '@shoelace-style/shoelace/dist/utilities/form.js';\n\nconst form = document.querySelector('#my-form');\nconst formControls = getFormControls(form);\n\nconsole.log(formControls); // e.g. [input, sl-input, ...]\n```\n\n:::tip\nYou probably don't need this function! If you're gathering form data for submission, you probably want to use [Data Serialization](#data-serializing) instead.\n:::\n"
  },
  {
    "path": "docs/pages/getting-started/installation.md",
    "content": "---\nmeta:\n  title: Installation\n  description: Choose the installation method that works best for you.\n---\n\n# Installation\n\nYou can load Shoelace via CDN or by installing it locally. If you're using a framework, make sure to check out the pages for [React](/frameworks/react), [Vue](/frameworks/vue), and [Angular](/frameworks/angular) for additional information.\n\n## CDN Installation (Easiest)\n\n<sl-tab-group>\n<sl-tab slot=\"nav\" panel=\"autoloader\" active>Autoloader</sl-tab>\n<sl-tab slot=\"nav\" panel=\"traditional\">Traditional Loader</sl-tab>\n\n<sl-tab-panel name=\"autoloader\">\n\nThe experimental autoloader is the easiest and most efficient way to use Shoelace. A lightweight script watches the DOM for unregistered Shoelace elements and lazy loads them for you — even if they're added dynamically.\n\nWhile convenient, autoloading may lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). The linked article describes some ways to alleviate it.\n\n<!-- prettier-ignore -->\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css\" />\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace-autoloader.js\"></script>\n```\n\n</sl-tab-panel>\n\n<sl-tab-panel name=\"traditional\">\n\nThe traditional CDN loader registers all Shoelace elements up front. Note that, if you're only using a handful of components, it will be much more efficient to stick with the autoloader. However, you can also [cherry pick](#cherry-picking) components if you want to load specific ones up front.\n\n<!-- prettier-ignore -->\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css\" />\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace.js\" ></script>\n```\n\n</sl-tab-panel>\n</sl-tab-group>\n\n### Dark Theme\n\nThe code above will load the light theme. If you want to use the [dark theme](/getting-started/themes#dark-theme) instead, update the stylesheet as shown below and add `<html class=\"sl-theme-dark\">` to your page.\n\n<!-- prettier-ignore -->\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css\" />\n```\n\n### Light & Dark Theme\n\nIf you want to load the light or dark theme based on the user's `prefers-color-scheme` setting, use the stylesheets below. The `media` attributes ensure that only the user's preferred theme stylesheet loads and the `onload` attribute sets the appropriate [theme class](/getting-started/themes) on the `<html>` element.\n\n```html\n<link\n  rel=\"stylesheet\"\n  media=\"(prefers-color-scheme:light)\"\n  href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css\"\n/>\n<link\n  rel=\"stylesheet\"\n  media=\"(prefers-color-scheme:dark)\"\n  href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css\"\n  onload=\"document.documentElement.classList.add('sl-theme-dark');\"\n/>\n```\n\nNow you can [start using Shoelace!](/getting-started/usage)\n\n## npm installation\n\nIf you don't want to use the CDN, you can install Shoelace from npm with the following command.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nIt's up to you to make the source files available to your app. One way to do this is to create a route in your app called `/shoelace` that serves static files from `node_modules/@shoelace-style/shoelace`.\n\nOnce you've done that, add the following tags to your page. Make sure to update `href` and `src` so they point to the route you created.\n\n```html\n<link rel=\"stylesheet\" href=\"/shoelace/%NPMDIR%/themes/light.css\" />\n<script type=\"module\" src=\"/shoelace/%NPMDIR%/shoelace.js\"></script>\n```\n\nAlternatively, [you can use a bundler](#bundling).\n\n:::tip\nFor clarity, the docs will usually show imports from `@shoelace-style/shoelace`. If you're not using a module resolver or bundler, you'll need to adjust these paths to point to the folder Shoelace is in.\n:::\n\n## Setting the Base Path\n\nSome components rely on assets (icons, images, etc.) and Shoelace needs to know where they're located. For convenience, Shoelace will try to auto-detect the correct location based on the script you've loaded it from. This assumes assets are colocated with `shoelace.js` or `shoelace-autoloader.js` and will \"just work\" for most users.\n\nHowever, if you're [cherry picking](#cherry-picking) or [bundling](#bundling) Shoelace, you'll need to set the base path. You can do this one of two ways.\n\n```html\n<!-- Option 1: the data-shoelace attribute -->\n<script src=\"bundle.js\" data-shoelace=\"/path/to/shoelace/%NPMDIR%\"></script>\n\n<!-- Option 2: the setBasePath() method -->\n<script src=\"bundle.js\"></script>\n<script type=\"module\">\n  import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';\n  setBasePath('/path/to/shoelace/%NPMDIR%');\n</script>\n```\n\n:::tip\nAn easy way to make sure the base path is configured properly is to check if [icons](/components/icon) are loading.\n:::\n\n### Referencing Assets\n\nMost of the magic behind assets is handled internally by Shoelace, but if you need to reference the base path for any reason, the same module exports a function called `getBasePath()`. An optional string argument can be passed, allowing you to get the full path to any asset.\n\n```html\n<script type=\"module\">\n  import { getBasePath, setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';\n\n  setBasePath('/path/to/assets');\n\n  // ...\n\n  // Get the base path, e.g. /path/to/assets\n  const basePath = getBasePath();\n\n  // Get the path to an asset, e.g. /path/to/assets/file.ext\n  const assetPath = getBasePath('file.ext');\n</script>\n```\n\n## Cherry Picking\n\nCherry picking can be done from [the CDN](#cdn-installation-easiest) or from [npm](#npm-installation). This approach will load only the components you need up front, while limiting the number of files the browser has to download. The disadvantage is that you need to import each individual component.\n\nHere's an example that loads only the button component. Again, if you're not using a module resolver, you'll need to adjust the path to point to the folder Shoelace is in.\n\n```html\n<link rel=\"stylesheet\" href=\"/path/to/shoelace/%NPMDIR%/themes/light.css\" />\n\n<script type=\"module\" data-shoelace=\"/path/to/shoelace/%NPMDIR%\">\n  import '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';\n\n  // <sl-button> is ready to use!\n</script>\n```\n\nYou can copy and paste the code to import a component from the \"Importing\" section of the component's documentation. Note that some components have dependencies that are automatically imported when you cherry pick. If a component has dependencies, they will be listed in the \"Dependencies\" section of its docs.\n\n:::warning\nNever cherry pick components or utilities from `shoelace.js` as this will cause the browser to load the entire library. Instead, cherry pick from specific modules as shown above.\n:::\n\n:::warning\nYou will see files named `chunk.[hash].js` in the `chunks` directory. Never import these files directly, as they are generated and change from version to version.\n:::\n\n## Bundling\n\nShoelace is distributed as a collection of standard ES modules that [all modern browsers can understand](https://caniuse.com/es6-module). However, importing a lot of modules can result in a lot of HTTP requests and potentially longer load times. Using a CDN can alleviate this, but some users may wish to further optimize their imports with a bundler.\n\nTo use Shoelace with a bundler, first install Shoelace along with your bundler of choice.\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\nNow it's time to configure your bundler. Configurations vary for each tool, but here are some examples to help you get started.\n\n- [Example webpack config](https://github.com/shoelace-style/webpack-example/blob/master/webpack.config.js)\n- [Example Rollup config](https://github.com/shoelace-style/rollup-example/blob/master/rollup.config.js)\n\nOnce your bundler is configured, you'll be able to import Shoelace components and utilities.\n\n```js\nimport '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';\nimport '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';\nimport '@shoelace-style/shoelace/%NPMDIR%/components/icon/icon.js';\nimport '@shoelace-style/shoelace/%NPMDIR%/components/input/input.js';\nimport '@shoelace-style/shoelace/%NPMDIR%/components/rating/rating.js';\nimport { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';\n\n// Set the base path to the folder you copied Shoelace's assets to\nsetBasePath('/path/to/shoelace/%NPMDIR%');\n\n// <sl-button>, <sl-icon>, <sl-input>, and <sl-rating> are ready to use!\n```\n\n:::warning\nComponent modules include side effects for registration purposes. Because of this, importing directly from `@shoelace-style/shoelace` may result in a larger bundle size than necessary. For optimal tree shaking, always cherry pick, i.e. import components and utilities from their respective files, as shown above.\n:::\n\n### Avoiding auto-registering imports\n\nBy default, imports to components will auto-register themselves. This may not be ideal in all cases. To import just the component's class without auto-registering it's tag we can do the following:\n\n```diff\n- import SlButton from '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';\n+ import SlButton from '@shoelace-style/shoelace/%NPMDIR%/components/button/button.component.js';\n```\n\nNotice how the import ends with `.component.js`. This is the current convention to convey the import does not register itself.\n\n:::danger\nWhile you can override the class or re-register the shoelace class under a different tag name, if you do so, many components won’t work as expected.\n:::\n\n## The difference between CDN and npm\n\nYou'll notice that the CDN links all start with `/%CDNDIR%/<path>` and npm imports use `/%NPMDIR%/<path>`. The `/%CDNDIR%` files are bundled separately from the `/%NPMDIR%` files. The `/%CDNDIR%` files come pre-bundled, which means all dependencies are inlined so you do not need to worry about loading additional libraries. The `/%NPMDIR%` files **DO NOT** come pre-bundled, allowing your bundler of choice to more efficiently deduplicate dependencies, resulting in smaller bundles and optimal code sharing.\n\nTL;DR:\n\n- `@shoelace-style/shoelace/%CDNDIR%` is for CDN users\n- `@shoelace-style/shoelace/%NPMDIR%` is for npm users\n\nThis change was introduced in `v2.5.0` to address issues around installations from npm loading multiple versions of libraries (such as the Lit) that Shoelace uses internally.\n"
  },
  {
    "path": "docs/pages/getting-started/localization.md",
    "content": "---\nmeta:\n  title: Localization\n  description: Discover how to localize Shoelace with minimal effort.\n---\n\n# Localization\n\nComponents can be localized by importing the appropriate translation file and setting the desired [`lang` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and/or [`dir` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) on the `<html>` element. Here's an example that renders Shoelace components in Spanish.\n\n```html\n<html lang=\"es\">\n  <head>\n    <script type=\"module\" src=\"/path/to/shoelace/dist/translations/es.js\"></script>\n  </head>\n\n  <body>\n    ...\n  </body>\n</html>\n```\n\nThrough the magic of a mutation observer, changing the `lang` attribute will automatically update all localized components to use the new locale.\n\n## Available Translations\n\nShoelace ships with a number of translations. The default is English (US), which also serves as the fallback locale. As such, you do not need to import the English translation. To see a list of all available translations in the latest version, [refer to this directory](https://github.com/shoelace-style/shoelace/tree/current/src/translations).\n\nThe location of translations depends on how you're consuming Shoelace.\n\n- If you're using the CDN, [import them from the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace?path=%CDNDIR%%2Ftranslations)\n- If you're using a bundler, import them from `@shoelace-style/shoelace/%NPMDIR%/translations/[lang].js`\n\nYou do not need to load translations up front. You can import them dynamically even after updating the `lang` attribute. Once a translation is registered, localized components will update automatically.\n\n```js\n// Same as setting <html lang=\"de\">\ndocument.documentElement.lang = 'de';\n\n// Import the translation\nimport('/path/to/shoelace/dist/translations/de.js');\n```\n\n### Translation Resolution\n\nThe locale set by `<html lang=\"...\">` is the default locale for the document. If a country code is provided, e.g. `es-PE` for Peruvian Spanish, the localization library will resolve it like this:\n\n1. Look for `es-PE`\n2. Look for `es`\n3. Fall back to `en`\n\nShoelace uses English as a fallback to provide a better experience than rendering nothing or throwing an error.\n\n### Submitting New Translations or Improvements\n\nTo contribute new translations or improvements to existing translations, please submit a pull request on GitHub. Translations are located in [`src/translations`](https://github.com/shoelace-style/shoelace/blob/next/src/translations) and can be edited directly on GitHub if you don't want to clone the repo locally.\n\nRegional translations are welcome! For example, if a German translation (`de`) exists it's perfectly acceptable to submit a German (Switzerland) (`de-CH`) translation.\n\nIf you have any questions, please start a [discussion](https://github.com/shoelace-style/shoelace/discussions) or ask in the [community chat](https://discord.gg/mg8f26C).\n\n:::tip\nShoelace provides a localization mechanism for component internals. This is not designed to be used as localization tool for your entire application. You should use a more appropriate tool such as [i18next](https://www.i18next.com/) if you need to localize content in your app.\n:::\n\n## Multiple Locales Per Page\n\nYou can use a different locale for an individual component by setting its `lang` and/or `dir` attributes. Here's a contrived example to demonstrate.\n\n```html\n<html lang=\"es\">\n  ...\n\n  <body>\n    <sl-button><!-- Spanish --></sl-button>\n    <sl-button lang=\"ru\"><!-- Russian --></sl-button>\n  </body>\n</html>\n```\n\nFor performance reasons, the `lang` and `dir` attributes must be on the component itself, not on an ancestor element.\n\n```html\n<html lang=\"es\">\n  ...\n\n  <body>\n    <div lang=\"ru\">\n      <sl-button><!-- still in Spanish --></sl-button>\n    </div>\n  </body>\n</html>\n```\n\nThis limitation exists because there's no efficient way to determine the current locale of a given element in a DOM tree. I consider this a gap in the platform and [I've proposed a couple properties](https://github.com/whatwg/html/issues/7039) to make this possible.\n\n## Creating Your Own Translations\n\nYou can provide your own translations if you have specific needs or if you don't want to wait for a translation to land upstream. The easiest way to do this is to copy `src/translations/en.ts` into your own project and translate the terms inside. When your translation is done, you can import it and use it just like a built-in translation.\n\nLet's create a Spanish translation as an example. The following assumes you're using TypeScript, but you can also create translations with regular JavaScript.\n\n```js\nimport { registerTranslation } from '@shoelace-style/shoelace/dist/utilities/localize';\nimport type { Translation } from '@shoelace-style/shoelace/dist/utilities/localize';\n\nconst translation: Translation = {\n  $code: 'es',\n  $name: 'Español',\n  $dir: 'ltr',\n\n  term1: '...',\n  term2: '...',\n  ...\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n```\n\nOnce your translation has been compiled to JavaScript, import it and activate it like this.\n\n```html\n<html lang=\"es\">\n  <head>\n    <script type=\"module\" src=\"/path/to/es.js\"></script>\n  </head>\n\n  <body>\n    ...\n  </body>\n</html>\n```\n\n:::tip\nIf your translation isn't working, make sure you're using the same localize module when importing `registerTranslation`. If you're using a different module, your translation won't be recognized.\n:::\n"
  },
  {
    "path": "docs/pages/getting-started/themes.md",
    "content": "---\nmeta:\n  title: Themes\n  description: Everything you need to know about theming Shoelace.\n---\n\n# Themes\n\nShoelace is designed to be highly customizable through pure CSS. Out of the box, you can choose from a light or dark theme. Alternatively, you can design your own theme.\n\nA theme is nothing more than a stylesheet that uses the Shoelace API to define design tokens and apply custom styles to components. To create a theme, you will need a decent understanding of CSS, including [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) and the [`::part` selector](https://developer.mozilla.org/en-US/docs/Web/CSS/::part).\n\n:::tip\nFor component developers, built-in themes are also available as JavaScript modules that export [Lit CSSResult](https://lit.dev/docs/api/styles/#CSSResult) objects. You can find them in `%NPMDIR%/themes/*.styles.js`.\n:::\n\n## Theme Basics\n\nAll themes are scoped to classes using the `sl-theme-{name}` convention, where `{name}` is a lowercase, hyphen-delimited value representing the name of the theme. The included light and dark themes use `sl-theme-light` and `sl-theme-dark`, respectively. A custom theme called \"Purple Power\", for example, would use a class called `sl-theme-purple-power`\n\nAll selectors must be scoped to the theme's class to ensure interoperability with other themes. You should also scope them to `:host` so they can be imported and applied to custom element shadow roots.\n\n```css\n:host,\n.sl-theme-purple-power {\n  /* ... */\n}\n```\n\n### Activating Themes\n\nTo activate a theme, import it and apply the theme's class to the `<html>` element. This example imports and activates the built-in dark theme.\n\n```html\n<html class=\"sl-theme-dark\">\n  <head>\n    <link rel=\"stylesheet\" href=\"path/to/shoelace/%NPMDIR%/themes/dark.css\" />\n  </head>\n\n  <body>\n    ...\n  </body>\n</html>\n```\n\n:::tip\nThere is one exception to this rule — the light theme _does not_ need to be activated. For convenience, the light theme is scoped to `:root` and will be activated by default when imported.\n:::\n\n### Using Multiple Themes\n\nYou can activate themes on various containers throughout the page. This example uses the light theme with a dark-themed sidebar.\n\n```html\n<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"path/to/shoelace/%NPMDIR%/themes/light.css\" />\n    <link rel=\"stylesheet\" href=\"path/to/shoelace/%NPMDIR%/themes/dark.css\" />\n  </head>\n\n  <body>\n    <nav class=\"sl-theme-dark\">\n      <!-- dark-themed sidebar -->\n    </nav>\n\n    <!-- light-themed content -->\n  </body>\n</html>\n```\n\nIt's for this reason that themes must be scoped to specific classes.\n\n## Creating Themes\n\nThere are two ways to create themes. The easiest way is to customize a built-in theme. The advanced way is to create a new theme from scratch. Which method you choose depends on your project's requirements and the amount of effort you're willing to commit to.\n\n### Customizing a Built-in Theme\n\nThe easiest way to customize Shoelace is to override one of the built-in themes. You can do this by importing the light or dark theme as-is, then creating a separate stylesheet that overrides [design tokens](/getting-started/customizing#design-tokens) and adds [component styles](/getting-started/customizing#component-parts) to your liking. You must import your theme _after_ the built-in theme.\n\nIf you're customizing the light theme, you should scope your styles to the following selectors.\n\n```css\n:root,\n:host,\n.sl-theme-light {\n  /* your custom styles here */\n}\n```\n\nIf you're customizing the dark theme, you should scope your styles to the following selectors.\n\n```css\n:host,\n.sl-theme-dark {\n  /* your custom styles here */\n}\n```\n\nBy customizing a built-in theme, you'll maintain a smaller stylesheet containing only the changes you've made. Contrast this to [creating a new theme](#creating-a-new-theme), where you need to explicitly define every design token required by the library. This approach is more \"future-proof,\" as new design tokens that emerge in subsequent versions of Shoelace will be accounted for by the built-in theme.\n\nWhile this approach is easier to maintain, the drawback is that your theme can't be activated independently — it's tied to the built-in theme you're extending.\n\n### Creating a New Theme\n\nCreating a new theme is more of an undertaking than [customizing an existing one](#customizing-a-built-in-theme). At a minimum, you must implement all of the required design tokens. The easiest way to do this is by \"forking\" one of the built-in themes and modifying it from there.\n\nStart by changing the selector to match your theme's name. Assuming your new theme is called \"Purple Power\", your theme should be scoped like this.\n\n```css\n:host,\n.sl-theme-purple-power {\n  /* your custom styles here */\n}\n```\n\nBy creating a new theme, you won't be relying on a built-in theme as a foundation. Because the theme is decoupled from the built-ins, you can activate it independently as an alternative to the built-ins. This is the recommended approach if you're looking to open source your theme for others to use.\n\nYou will, however, need to maintain your theme more carefully, as new versions of Shoelace may introduce new design tokens that your theme won't have accounted for. Because of this, it's recommended that you clearly specify which version(s) of Shoelace your theme is designed to work with and keep it up to date as new versions of Shoelace are released.\n\n## Dark Theme\n\nThe built-in dark theme uses an inverted color scale so, if you're using design tokens as intended, you'll get dark mode for free. While this isn't the same as a professionally curated dark theme, it provides an excellent baseline for one and you're encouraged to customize it depending on your needs.\n\nThe dark theme works by taking the light theme's [color tokens](/tokens/color) and \"flipping\" the scale so 100 becomes 900, 200 becomes 800, 300 becomes 700, etc. Next, the luminance of each primitive was fine-tuned to avoid true black, which is often undesirable in dark themes, and provide a richer experience. The result is a custom dark palette that complements the light theme and makes it easy to offer light and dark modes with minimal effort.\n\nTo install the dark theme, add the following to the `<head>` section of your page.\n\n```html\n<link\n  rel=\"stylesheet\"\n  href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css\"\n/>\n```\n\nTo activate the theme, apply the `sl-theme-dark` class to the `<html>` element.\n\n```html\n<html class=\"sl-theme-dark\">\n  ...\n</html>\n```\n\n### Detecting the User's Color Scheme Preference\n\nShoelace doesn't try to auto-detect the user's light/dark mode preference. This should be done at the application level. As a best practice, to provide a dark theme in your app, you should:\n\n- Check for [`prefers-color-scheme`](https://stackoverflow.com/a/57795495/567486) and use its value by default\n- Allow the user to override the setting in your app\n- Remember the user's preference and restore it on subsequent logins\n\nShoelace avoids using the `prefers-color-scheme` media query because not all apps support dark mode, and it would break things for the ones that don't.\n"
  },
  {
    "path": "docs/pages/getting-started/usage.md",
    "content": "---\nmeta:\n  title: Usage\n  description: Learn more about using custom elements.\n---\n\n# Usage\n\nShoelace components are just regular HTML elements, or [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) to be precise. You can use them like any other element. Each component has detailed documentation that describes its full API, including properties, events, methods, and more.\n\nIf you're new to custom elements, often referred to as \"web components,\" this section will familiarize you with how to use them.\n\n## Attributes & Properties\n\nMany components have properties that can be set using attributes. For example, buttons accept a `size` attribute that maps to the `size` property which dictates the button's size.\n\n```html\n<sl-button size=\"small\">Click me</sl-button>\n```\n\nSome properties are boolean, so they only have true/false values. To activate a boolean property, add the corresponding attribute without a value.\n\n```html\n<sl-button disabled>Click me</sl-button>\n```\n\nIn rare cases, a property may require an array, an object, or a function. For example, to customize the color picker's list of preset swatches, you set the `swatches` property to an array of colors. This must be done with JavaScript.\n\n```html\n<sl-color-picker></sl-color-picker>\n\n<script>\n  const colorPicker = document.querySelector('sl-color-picker');\n  colorPicker.swatches = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];\n</script>\n```\n\nRefer to a component's documentation for a complete list of its properties.\n\n## Events\n\nYou can listen for standard events such as `click`, `mouseover`, etc. as you normally would. However, it's important to note that many events emitted within a component's shadow root will be [retargeted](https://dom.spec.whatwg.org/#retarget) to the host element. This may result in, for example, multiple `click` handlers executing even if the user clicks just once. Furthermore, `event.target` will point to the host element, making things even more confusing.\n\nAs a result, you should almost always listen for custom events instead. For example, instead of listening to `click` to determine when an `<sl-checkbox>` gets toggled, listen to `sl-change`.\n\n```html\n<sl-checkbox>Check me</sl-checkbox>\n\n<script>\n  const checkbox = document.querySelector('sl-checkbox');\n  checkbox.addEventListener('sl-change', event => {\n    console.log(event.target.checked ? 'checked' : 'not checked');\n  });\n</script>\n```\n\nAll custom events are prefixed with `sl-` to prevent collisions with standard events and other libraries. Refer to a component's documentation for a complete list of its custom events.\n\n## Methods\n\nSome components have methods you can call to trigger various behaviors. For example, you can set focus on a Shoelace input using the `focus()` method.\n\n```html\n<sl-input></sl-input>\n\n<script>\n  const input = document.querySelector('sl-input');\n  input.focus();\n</script>\n```\n\nRefer to a component's documentation for a complete list of its methods and their arguments.\n\n## Slots\n\nMany components use slots to accept content inside of them. The most common slot is the _default_ slot, which includes any content inside the component that doesn't have a `slot` attribute.\n\nFor example, a button's default slot is used to populate its label.\n\n```html\n<sl-button>Click me</sl-button>\n```\n\nSome components also have _named_ slots. A named slot can be populated by adding a child element with the appropriate `slot` attribute. Notice how the icon below has the `slot=\"prefix\"` attribute? This tells the component to place the icon into its `prefix` slot.\n\n```html\n<sl-button>\n  <sl-icon slot=\"prefix\" name=\"gear\"></sl-icon>\n  Settings\n</sl-button>\n```\n\nThe location of a named slot doesn't matter. You can put it anywhere inside the component and the browser will move it to the right place automatically!\n\nRefer to a component's documentation for a complete list of available slots.\n\n## Don't Use Self-closing Tags\n\nCustom elements cannot have self-closing tags. Similar to `<script>` and `<textarea>`, you must always include the full closing tag.\n\n```html\n<!-- Don't do this -->\n<sl-input />\n\n<!-- Always do this -->\n<sl-input></sl-input>\n```\n\n## Differences from Native Elements\n\nYou might expect similarly named elements to share the same API as native HTML elements, but this is not always the case. Shoelace components **are not** designed to be one-to-one replacements for their HTML counterparts. While they usually share the same API, there may be subtle differences.\n\nFor example, `<button>` and `<sl-button>` both have a `type` attribute, but the native one defaults to `submit` while the Shoelace one defaults to `button` since this is a better default for most users.\n\n:::tip\n**Don't make assumptions about a component's API!** To prevent unexpected behaviors, please take the time to review the documentation and make sure you understand what each attribute, property, method, and event is intended to do.\n:::\n\n## Waiting for Components to Load\n\nWeb components are registered with JavaScript, so depending on how and when you load Shoelace, you may notice a [Flash of Undefined Custom Elements (FOUCE)](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/) when the page loads. There are a couple ways to prevent this, both of which are described in the linked article.\n\nOne option is to use the [`:defined`](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) CSS pseudo-class to \"hide\" custom elements that haven't been registered yet. You can scope it to specific tags or you can hide all undefined custom elements as shown below.\n\n```css\n:not(:defined) {\n  visibility: hidden;\n}\n```\n\nAs soon as a custom element is registered, it will immediately appear with all of its styles, effectively eliminating FOUCE. Note the use of `visibility: hidden` instead of `display: none` to reduce shifting as elements are registered. The drawback to this approach is that custom elements can potentially appear one by one instead of all at the same time.\n\nAnother option is to use [`customElements.whenDefined()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined), which returns a promise that resolves when the specified element gets registered. You'll probably want to use it with [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) in case an element fails to load for some reason.\n\nA clever way to use this method is to hide the `<body>` with `opacity: 0` and add a class that fades it in as soon as all your custom elements are defined.\n\n```html\n<style>\n  body {\n    opacity: 0;\n  }\n\n  body.ready {\n    opacity: 1;\n    transition: 0.25s opacity;\n  }\n</style>\n\n<script type=\"module\">\n  await Promise.allSettled([\n    customElements.whenDefined('sl-button'),\n    customElements.whenDefined('sl-card'),\n    customElements.whenDefined('sl-rating')\n  ]);\n\n  // Button, card, and rating are registered now! Add\n  // the `ready` class so the UI fades in.\n  document.body.classList.add('ready');\n</script>\n```\n\n## Component Rendering and Updating\n\nShoelace components are built with [Lit](https://lit.dev/), a tiny library that makes authoring custom elements easier, more maintainable, and a lot of fun! As a Shoelace user, here is some helpful information about rendering and updating you should probably be aware of.\n\nTo optimize performance and reduce re-renders, Lit batches component updates. This means changing multiple attributes or properties at the same time will result in just a single re-render. In most cases, this isn't an issue, but there may be times you'll need to wait for the component to update before continuing.\n\nConsider this example. We're going to change the `checked` property of the checkbox and observe its corresponding `checked` attribute, which happens to reflect.\n\n```js\nconst checkbox = document.querySelector('sl-checkbox');\ncheckbox.checked = true;\n\nconsole.log(checkbox.hasAttribute('checked')); // false\n```\n\nMost developers will expect this to be `true` instead of `false`, but the component hasn't had a chance to re-render yet so the attribute doesn't exist when `hasAttribute()` is called. Since changes are batched, we need to wait for the update before proceeding. This can be done using the `updateComplete` property, which is available on all Lit-based components.\n\n```js\nconst checkbox = document.querySelector('sl-checkbox');\ncheckbox.checked = true;\n\ncheckbox.updateComplete.then(() => {\n  console.log(checkbox.hasAttribute('checked')); // true\n});\n```\n\nThis time we see an empty string, which means the boolean attribute is now present!\n\n:::tip\nAvoid using `setTimeout()` or `requestAnimationFrame()` in situations like this. They might work, but it's far more reliable to use `updateComplete` instead.\n:::\n\n## Code Completion\n\n### VS Code\n\nShoelace ships with a file called `vscode.html-custom-data.json` that can be used to describe its custom elements to Visual Studio Code. This enables code completion for Shoelace components (also known as \"code hinting\" or \"IntelliSense\"). To enable it, you need to tell VS Code where the file is.\n\n1. [Install Shoelace locally](/getting-started/installation#local-installation)\n2. If it doesn't already exist, create a folder called `.vscode` at the root of your project\n3. If it doesn't already exist, create a file inside that folder called `settings.json`\n4. Add the following to the file\n\n```js\n{\n  \"html.customData\": [\"./node_modules/@shoelace-style/shoelace/dist/vscode.html-custom-data.json\"]\n}\n```\n\nIf `settings.json` already exists, simply add the above line to the root of the object. Note that you may need to restart VS Code for the changes to take effect.\n\n### JetBrains IDEs\n\nIf you are using a [JetBrains IDE](https://www.jetbrains.com/) and you are installing Shoelace from NPM, the editor will automatically detect the `web-types.json` file from the package and you should immediately see component information in your editor.\n\nIf you are installing from the CDN, you can [download a local copy](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/dist/web-types.json) and add it to the root of your project. Be sure to add a reference to the `web-types.json` file in your `package.json` in order for your editor to properly detect it.\n\n```json\n{\n  ...\n  \"web-types\": \"./web-types.json\"\n  ...\n}\n```\n\nIf you are using types from multiple projects, you can add an array of references.\n\n```json\n{\n  ...\n  \"web-types\": [\n    ...,\n    \"./web-types.json\"\n  ]\n  ...\n}\n```\n\n### Other Editors\n\nMost popular editors support custom code completion with a bit of configuration. Please [submit a feature request](https://github.com/shoelace-style/shoelace/issues/new/choose) for your editor of choice. PRs are also welcome!\n"
  },
  {
    "path": "docs/pages/index.md",
    "content": "---\nmeta:\n  title: 'Shoelace: A forward-thinking library of web components.'\n  description: Hand-crafted custom elements for any occasion.\ntoc: false\n---\n\n<div class=\"splash\">\n<div class=\"splash-start\">\n<img class=\"splash-logo\" src=\"/assets/images/wordmark.svg\" alt=\"Shoelace\">\n\n# <sl-visually-hidden>Shoelace:</sl-visually-hidden> A forward-thinking library of web components.\n\n- Works with all frameworks 🧩\n- Works with CDNs 🚛\n- Fully customizable with CSS 🎨\n- Includes a dark theme 🌛\n- Built with accessibility in mind ♿️\n- First-class [React support](/frameworks/react) ⚛️\n- Built-in localization 💬\n- Open source 😸\n- [More awesome than ever](https://blog.fontawesome.com/shoelace-joins-font-awesome/) ![Awesome emoji](/assets/images/awesome.svg)\n\n</div>\n<div class=\"splash-end\">\n<img class=\"splash-image\" src=\"/assets/images/undraw-content-team.svg\" alt=\"Cartoon of people assembling components while standing on a giant laptop.\">\n</div>\n</div>\n\n<div class=\"badges\">\n\n[![jsDelivr](https://data.jsdelivr.com/v1/package/npm/@shoelace-style/shoelace/badge)](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace)\n[![npm](https://img.shields.io/npm/dw/@shoelace-style/shoelace?label=npm&style=flat-square)](https://www.npmjs.com/package/@shoelace-style/shoelace)\n[![License](https://img.shields.io/badge/license-MIT-232323.svg?style=flat-square)](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md)<br>\n[![Discord](https://img.shields.io/badge/Discord-Join%20the%20chat-5965f2.svg?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/mg8f26C)\n[![Twitter](https://img.shields.io/badge/Twitter-Follow-00acee.svg?style=flat-square&logo=twitter&logoColor=white)](https://twitter.com/shoelace_style)\n[![Sponsor](https://img.shields.io/badge/GitHub-Code-232323.svg?style=flat-square&logo=github&logoColor=white)](https://github.com/shoelace-style/shoelace)\n\n</div>\n\n## Quick Start\n\nAdd the following code to your page.\n\n<!-- prettier-ignore -->\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css\" />\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace-autoloader.js\"></script>\n```\n\nNow you have access to all of Shoelace's components! Try adding a button:\n\n```html:preview:expanded:no-codepen\n<sl-button>Click me</sl-button>\n```\n\n:::tip\nThis will activate Shoelace's experimental autoloader, which registers components on the fly as you use them. To learn more about it, or for other ways to install Shoelace, refer to the [installation instructions](getting-started/installation).\n:::\n\n## New to Web Components?\n\n**TL;DR** – we finally have a way to create [our own HTML elements](https://html.spec.whatwg.org/multipage/custom-elements.html) and use them in any framework we want!\n\nThanks to the popularity of frameworks such as Angular, Vue, and React, component-driven development has become a part of our every day lives. Components help us encapsulate styles and behaviors into reusable building blocks. They make a lot of sense in terms of design, development, and testing.\n\nUnfortunately, _framework-specific_ components fail us in a number of ways:\n\n- You can only use them in the framework they're designed for 🔒\n- Their lifespan is limited to that of the framework's ⏳\n- New frameworks/versions can lead to breaking changes, requiring substantial effort to update components 😭\n\nWeb components solve these problems. They're [supported by all modern browsers](https://caniuse.com/#feat=custom-elementsv1), they're framework-agnostic, and they're [part of the standard](https://developer.mozilla.org/en-US/docs/Web/Web_Components), so we know they'll be supported for many years to come.\n\nThis is the technology that Shoelace is built on.\n\n## What Problem Does This Solve?\n\nShoelace provides a collection of professionally designed, highly customizable UI components built on a framework agnostic technology. Why spend hundreds of hours (or more) building a design system from scratch? Why make a component library that only works with one framework?\n\nWith Shoelace, you can:\n\n- Start building things faster (no need to roll your own buttons)\n- Build multiple apps with different frameworks that all share the same UI components\n- Fully customize components to match your existing designs\n- Incrementally adopt components as needed (no need to ditch your framework)\n- Upgrade or switch frameworks without rebuilding foundational components\n\nIf your organization is looking to build a design system, [Shoelace will save you thousands of dollars](https://medium.com/eightshapes-llc/and-you-thought-buttons-were-easy-26eb5b5c1871). All the foundational components you need are right here, ready to be customized for your brand. And since it's built on web standards, browsers will continue to support it for many years to come.\n\nWhether you use Shoelace as a starting point for your organization's design system or for a fun personal project, there's no limit to what you can do with it.\n\n## Browser Support\n\nShoelace is tested in the latest two versions of the following browsers.\n\n<img src=\"/assets/images/chrome.png\" alt=\"Chrome\" width=\"64\" height=\"64\">\n<img src=\"/assets/images/edge.png\" alt=\"Edge\" width=\"64\" height=\"64\">\n<img src=\"/assets/images/firefox.png\" alt=\"Firefox\" width=\"64\" height=\"64\">\n<img src=\"/assets/images/opera.png\" alt=\"Opera\" width=\"64\" height=\"64\">\n<img src=\"/assets/images/safari.png\" alt=\"Safari\" width=\"64\" height=\"64\">\n\nCritical bug fixes in earlier versions will be addressed based on their severity and impact.\n\nIf you need to support IE11 or pre-Chromium Edge, this library isn't for you. Although web components can (to some degree) be polyfilled for legacy browsers, supporting them is outside the scope of this project. If you're using Shoelace in such a browser, you're gonna have a bad time. ⛷\n\n## License\n\nShoelace was created in New Hampshire by [Cory LaViska](https://twitter.com/cory_laviska). It's available under the terms of the [MIT license](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md).\n\n## Attribution\n\nSpecial thanks to the following projects and individuals that help make Shoelace possible.\n\n- Components are built with [Lit](https://lit.dev/)\n- Component metadata is generated by the [Custom Elements Manifest Analyzer](https://github.com/open-wc/custom-elements-manifest)\n- Documentation is powered by [11ty](https://www.11ty.dev/)\n- CDN services are provided by [jsDelivr](https://www.jsdelivr.com/)\n- Color primitives are inspired by [Tailwind](https://tailwindcss.com/)\n- Icons are courtesy of [Bootstrap Icons](https://icons.getbootstrap.com/)\n- The homepage illustration is courtesy of [unDraw](https://undraw.co/)\n- Positioning of dropdowns, tooltips, et al is handled by [Floating UI](https://floating-ui.com/)\n- Animations are courtesy of [animate.css](https://animate.style/)\n- Search is powered by [Lunr](https://lunrjs.com/)\n- The Shoelace logo was designed with a single shoelace by [Adam K Olson](https://twitter.com/adamkolson)\n"
  },
  {
    "path": "docs/pages/resources/accessibility.md",
    "content": "---\nmeta:\n  title: Accessibility Commitment\n  description: Shoelace recognizes the need for all users to have undeterred access to the websites and applications that are created with it.\n---\n\n# Accessibility Commitment\n\nShoelace recognizes the need for all users, regardless of ability and device, to have undeterred access to the websites and applications that are created with it. This is an important goal of the project.\n\nOftentimes, people will ask “is Shoelace accessible?” I’m reluctant to answer because accessibility isn’t binary — there’s no simple “yes” or “no” response to provide. What seems accessible to a sighted user might be completely inaccessible to a non-sighted user. And even if you optimize for various screen readers, you still have to account for low-level vision, color blindness, hearing impairments, mobility impairments, and more.\n\nAccessibility is something you have to continuously strive for. No individual contributor — or perhaps even an entire team — can claim their software is 100% accessible because of the sheer diversity of abilities, devices, assistive technologies, and individual use cases.\n\nFurthermore, accessibility doesn’t stop at the component level. Using accessible building blocks doesn’t magically make the rest of your webpage or application compliant. There is no library or overlay that will make your software “fully accessible” without putting in the effort. It’s also worth noting that web components are still somewhat bleeding edge, so browsers, assistive devices, and [even specifications](https://wicg.github.io/aom/spec/) are still evolving to help improve accessibility on the web platform.\n\nMy commitment to Shoelace users is this: Everything I develop will be built with accessibility in mind. I will test and improve every component to the best of my ability and knowledge. I will work around upstream issues, such as browser bugs and limitations, to the best of my ability and within reason.\n\nI’m fully aware that I may not get it right every time for every user, so I invite the community to participate in this ongoing effort by submitting [issues](https://github.com/shoelace-style/shoelace/issues?q=is%3Aissue+is%3Aopen+label%3Aa11y), [pull requests](https://github.com/shoelace-style/shoelace/pulls?q=is%3Aopen+is%3Apr+label%3Aa11y), and [discussions](https://github.com/shoelace-style/shoelace/discussions). Many accessibility improvements have already been made thanks to contributors submitting code, feedback, and suggestions.\n\nThis is the path forward. Together, we will continue to make Shoelace accessible to as many users as possible.\n\n— Cory LaViska<br>\n_Creator of Shoelace_\n"
  },
  {
    "path": "docs/pages/resources/changelog.md",
    "content": "---\nmeta:\n  title: Changelog\n  description: Changes to each version of the project are documented here.\n---\n\n# Changelog\n\nShoelace follows [Semantic Versioning](https://semver.org/). Breaking changes in components with the <sl-badge variant=\"primary\" pill>Stable</sl-badge> badge will not be accepted until the next major version. As such, all contributions must consider the project's roadmap and take this into consideration. Features that are deemed no longer necessary will be deprecated but not removed.\n\nComponents with the <sl-badge variant=\"warning\" pill>Experimental</sl-badge> badge should not be used in production. They are made available as release candidates for development and testing purposes. As such, changes to experimental components will not be subject to semantic versioning.\n\nNew versions of Shoelace are released as-needed and generally occur when a critical mass of changes have accumulated. At any time, you can see what's coming in the next release by visiting [next.shoelace.style](https://next.shoelace.style).\n\n## 2.20.1\n\n- Fixed a bug that prevented `<sl-tab-group>` to be activated properly when rendered in another `<sl-tab-group>` [#2367]\n- Fixed a bug that in `<sl-dropdown>` that prevented tab from working properly in some cases [#2371]\n- Fixed the guard on popover to allow virtual elements [#2399]\n- Fixed the close button in `<sl-alert>` so clicking above/below it doesn't inadvertently close it [#2375]\n- Fixed accessibility issues for elements that are closed while having slotted focused children. [#2383]\n- Improved accessibility of `<sl-carousel>` [#2364]\n\n## 2.20.0\n\n- Added the ability to set a custom snap function and use `repeat(n)` to `<sl-split-panel>` [#2340]\n- Fixed a bug with radios in `<sl-dialog>` focus trapping.\n- Improved performance of `<sl-select>` when using a large number of options [#2318]\n- Improved `<sl-alert>` to create the toast stack when used only, making it usable in SSR environments [#2359]\n- Improved `scrollend-polyfill` so it only runs on the client to make it usable in SSR environments [#2359]\n- Updated the Japanese translation [#2329]\n\n## 2.19.1\n\n- Fixed a bug in `<sl-tab-group>` that prevented changing tabs by setting `active` on `<sl-tab>` elements [#2298]\n\n## 2.19.0\n\n- Added Norwegian translations for Bokmål and Nynorsk [#2268]\n- Added Ukrainian translation [#2270]\n- Added community maintained docs for Svelte [#2262]\n- Fixed a bug in `<sl-select>` when setting the value property before the element connected [#2255]\n- Fixed a bug in `<sl-select>` where it was using the wrong tag name [#2287]\n- Fixed a bug in `<sl-carousel>` that caused the navigation icons to be reversed\n- Fixed a bug in `<sl-carousel>` that caused interactive elements to be activated when dragging [#2196]\n- Fixed a bug in `<sl-carousel>` that caused out of order slides when used inside a resize observer [#2260]\n- Fixed a bug in `<sl-rating>` that allowed tabbing into the rating when readonly [#2271]\n- Fixed a bug in `<sl-select>` where it was using the wrong tag name [#2287]\n- Fixed a bug in `<sl-select>` that prevented label changes in `<sl-option>` from updating the controller [#1971]\n- Fixed a bug in `<sl-select>` that caused the placeholder to display incorrectly when using `placeholder` and `multiple` [#2292]\n- Fixed a bug in `<sl-textarea>` that caused a console warning in Firefox when typing [#2107]\n- Improved accessibility of `<sl-split-panel>` by adding support for <kbd>Enter</kbd> to align with ARIA APG's [window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/) [#2234]\n- Improved performance of `<sl-range>` by skipping positioning logic when tooltip isn't shown [#2064]\n- Updated many dependencies to their latest versions\n\n## 2.18.0\n\n- Added Finnish translation [#2211]\n- Added the `.focus` function to `<sl-radio-group>` [#2192]\n- Fixed a bug in `<sl-tab-group>` that caused an error when removed from the DOM too quickly [#2218]\n- Fixed a bug with `<sl-select>` not respecting its initial value [#2204]\n- Fixed a bug with certain bundlers when using dynamic imports [#2210]\n- Fixed a bug in `<sl-textarea>` causing scroll jumping when using `resize=\"auto\"` [#2182]\n- Fixed a bug in `<sl-relative-time>` where the title attribute would show with redundant info [#2184]\n- Fixed a bug in `<sl-select>` that caused multi-selects without placeholders to have the wrong padding [#2194]\n- Fixed a bug in `<sl-tooltip>` that caused a memory leak in disconnected elements [#2226]\n- Fixed a bug in `<sl-select>` that caused an exception in an edge case using Edge + autofill [#2221]\n- Improved the behavior of navigation dots in `<sl-carousel>` [#2220]\n- Updated all checks for directionality to use `this.localize.dir()` instead of `el.matches(:dir(rtl))` so older browsers don't error out [#2188]\n\n## 2.17.1\n\n- Fixed a bug in `<sl-icon>` not applying the mutator when loading multiple icons of the same name from a sprite sheet [#2178]\n- Fixed a bug in `<sl-select>` that made the suffix slot collide with the clear button [#2145]\n- Improved performance of `<sl-popup>` by waiting for the active state before spinning up the positioning library [#2179]\n\n## 2.17.0\n\n- Added the `fixed-scroll-controls` attribute to `<sl-tab-group>` [#2128]\n- Added support for using `<sl-dropdown>` in `<sl-breadcrumb-item>` default slot [#2015]\n- Added the `countdown` attribute to `<sl-alert>` to show a visual indicator before the toast disappears [#1899]\n- Fixed a bug with morphing and DOM diffing that would cause elements with reflected initial attributes to not reset [#2177]\n- Fixed a bug that caused errors to show in the console when components disconnect before before `firstUpdated()` executes [#2127]\n- Fixed a bug that made pagination work incorrectly in `<sl-carousel>` [#2155]\n- Fixed a bug in `<sl-tab-group>` that caused the active tab indicator to be the wrong size when the tab's content changes [#2164]\n- Fixed a bug in `<sl-select>` that caused the prefix icon to have incorrect spacing [#2167]\n- Fixed a bug in `<sl-button>` that prevented link buttons from being disabled [#2151]\n\n## 2.16.0\n\n- Added the Czech translation [#2084]\n- Added the `base__popup` part to `<sl-dropdown>` [#2078]\n- Added the `suffix` slot and corresponding part to `<sl-select>` [#2063]\n- `<sl-tab>` `closable` property now reflects [#2041]\n- `<sl-tab-group>` now implements a proper \"roving tabindex\" and `<sl-tab>` is no longer tabbable by default. This aligns closer to the APG pattern for tabs [#2041]\n- Fixed a bug in `<sl-menu>` that did not allow checkboxes to be checked that were in submenus [#2116]\n- Fixed a bug in `<sl-details>` / `<sl-drawer>` that was accidentally detecting overflows and showing a scrollbar [#2121]\n- Fixed a bug in the submenu controller that prevented submenus from rendering in RTL without explicitly setting `dir` on the parent menu item [#1992]\n- Fixed a bug where `<sl-relative-time>` would announce the full time instead of the relative time in screen readers\n- When calling `customElements.define` we no longer register with anonymous classes by default [#2079]\n- When avatar image load fails, send error event [#2122]\n\n## 2.15.1\n\n- Fixed a bug in `<sl-radio-group>` where if a click did not contain a `<sl-radio>` it would show a console error [#2009]\n- Fixed a bug in `<sl-split-panel>` that caused it not to recalculate it's position when going from being `display: none;` to its original display value [#1942]\n- Fixed a bug in `<dialog>` where when it showed it would cause a layout shift [#1967]\n- Fixed a bug in `<sl-tooltip>` that allowed unwanted text properties to leak in [#1947]\n- Fixed a bug in `<sl-button-group>` classes [#1974]\n- Fixed a bug in `<sl-textarea>` that may throw errors on `disconnectedCallback` in test environments [#1985]\n- Fixed a bug in `<sl-color-picker>` that would log a non-passive event listener warning [#2005]\n- Fixed a bug in the submenu controller that allowed submenus to go offscreen and not be scrollable [#2001]\n- Fixed a bug in `<sl-range>` that caused the tooltip position to be incorrect in some cases [#1979]\n\n## 2.15.0\n\n- Added the Slovenian translation [#1893]\n- Added support for `contextElement` to `VirtualElements` in `<sl-popup>` [#1874]\n- Added the `spinner` and `spinner__base` parts to `<sl-tree-item>` [#1937]\n- Added the `sync` property to `<sl-dropdown>` so the menu can easily sync sizes with the trigger element [#1935]\n- Fixed a bug in `<sl-icon>` that did not properly apply mutators to spritesheets [#1927]\n- Fixed a bug in `.sl-scroll-lock` causing layout shifts [#1895]\n- Fixed a bug in `<sl-rating>` that caused the rating to not reset in some circumstances [#1877]\n- Fixed a bug in `<sl-select>` that caused the menu to not close when rendered in a shadow root [#1878]\n- Fixed a bug in `<sl-tree>` that caused a new stacking context resulting in tooltips being clipped [#1709]\n- Fixed a bug in `<sl-tab-group>` that caused the scroll controls to toggle indefinitely when zoomed in Safari [#1839]\n- Fixed a bug in the submenu controller that allowed two submenus to be open at the same time [#1880]\n- Fixed a bug in `<sl-select>` where the tag size wouldn't update with the control's size [#1886]\n- Fixed a bug in `<sl-checkbox>` and `<sl-switch>` where the color of the required content wasn't applying correctly\n- Fixed a bug in `<sl-checkbox>` where help text was incorrectly styled [#1897]\n- Fixed a bug in `<sl-input>` that prevented the control from receiving focus when clicking over the clear button\n- Fixed a bug in `<sl-carousel>` that caused the carousel to be out of sync when used with reduced motion settings [#1887]\n- Fixed a bug in `<sl-button-group>` that caused styles to stop working when using `className` on buttons in React [#1926]\n\n## 2.14.0\n\n- Added the Arabic translation [#1852]\n- Added help text to `<sl-checkbox>` [#1860]\n- Added help text to `<sl-switch>` [#1800]\n- Fixed a bug in `<sl-option>` that caused HTML tags to be included in `getTextLabel()`\n- Fixed a bug in `<sl-carousel>` that caused slides to not switch correctly [#1862]\n- Refactored component styles to be consumed more efficiently [#1692]\n\n## 2.13.1\n\n- Fixed a bug where the safe triangle was always visible when selecting nested `<sl-menu>` elements [#1835]\n\n## 2.13.0\n\n- Added the `hover-bridge` feature to `<sl-popup>` to support better tooltip accessibility [#1734]\n- Added the `loading` attribute and the `spinner` and `spinner__base` part to `<sl-menu-item>` [#1700]\n- Fixed files that did not have `.js` extensions [#1770]\n- Fixed a bug in `<sl-tree>` when providing custom expand / collapse icons [#1922]\n- Fixed `<sl-dialog>` not accounting for elements with hidden dialog controls like `<video>` [#1755]\n- Fixed focus trapping not scrolling elements into view [#1750]\n- Fixed more performance issues with focus trapping performance [#1750]\n- Fixed a bug in `<sl-input>` and `<sl-textarea>` that made it work differently from `<input>` and `<textarea>` when using defaults [#1746]\n- Fixed a bug in `<sl-select>` that prevented it from closing when tabbing to another select inside a shadow root [#1763]\n- Fixed a bug in `<sl-spinner>` that caused the animation to appear strange in certain circumstances [#1787]\n- Fixed a bug in `<sl-dialog>` with focus trapping [#1813]\n- Fixed a bug that caused form controls to submit even after they were removed from the DOM [#1823]\n- Fixed a bug that caused empty `<sl-radio-group>` elements to log an error in the console [#1795]\n- Fixed a bug that caused modal scroll locking to conflict with the `scrollbar-gutter` property [#1805]\n- Fixed a bug in `<sl-option>` that caused slotted content to show up when calling `getTextLabel()` [#1730]\n- Fixed a bug in `<sl-color-picker>` that caused picker values to not match the preview color [#1831]\n- Fixed a bug in `<sl-carousel>` where pagination dots don't update when swiping slide in iOS Safari [#1748]\n- Fixed a bug in`<sl-carousel>` where trying to swipe doesn't change the slide in Firefox for Android [#1748]\n- Improved the accessibility of `<sl-tooltip>` so they persist when hovering over the tooltip and dismiss when pressing [[Esc]] [#1734]\n- Improved \"close\" behavior of multiple components in supportive browsers using the `CloseWatcher` API [#1788]\n- Removed the scroll controller from the experimental `<sl-carousel>` and moved all mouse related logic into the component [#1748]\n\n## 2.12.0\n\n- Added the Italian translation [#1727]\n- Added the ability to call `form.checkValidity()` and it will use Shoelace's custom `checkValidity()` handler [#1708]\n- Fixed a bug where nested dialogs were not properly trapping focus [#1711]\n- Fixed a bug with form controls removing the custom validity handlers from the form [#1708]\n- Fixed a bug in form control components that used a `form` property, but not an attribute [#1707]\n- Fixed a bug with bundled components using CDN builds not having translations on initial connect [#1696]\n- Fixed a bug where the `\"sl-change\"` event would always fire simultaneously with `\"sl-input\"` event in `<sl-color-picker>`. The `<sl-change>` event now only fires when a user stops dragging a slider or stops dragging on the color canvas [#1689]\n- Updated the copy icon in the system library [#1702]\n\n## 2.11.2\n\n- Fixed a bug in `<sl-carousel>` component that caused an error to be thrown when rendered with Lit [#1684]\n\n## 2.11.1\n\n- Improved the experimental `<sl-carousel>` component [#1605]\n\n## 2.11.0\n\n- Added the Croatian translation [#1656]\n- Fixed a bug that caused the [[Escape]] key to stop propagating when tooltips are disabled [#1607]\n- Fixed a bug that made it impossible to style placeholders in `<sl-select>` [#1667]\n- Fixed a bug that caused `dist/react/index.js` to be blank [#1659]\n\n## 2.10.0\n\n- Added the Simplified Chinese translation [#1604]\n- Fixed a bug [in the localize dependency](https://github.com/shoelace-style/localize/issues/20) that caused underscores in language codes to throw a `RangeError`\n- Fixed a bug in the focus trapping utility used by modals that caused unexpected focus behavior [#1583]\n- Fixed a bug in `<sl-copy-button>` that prevented exported tooltip parts from being styled [#1586]\n- Fixed a bug in `<sl-menu>` that caused it not to fire the `sl-select` event if you clicked an element inside of a `<sl-menu-item>` [#1599]\n- Fixed a bug that caused focus trap logic to hang the browser in certain circumstances [#1612]\n- Improved submenu selection by implementing the [safe triangle](https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/) method [#1550]\n- Updated `@shoelace-style/localize` to 3.1.0\n- Updated `@lib-labs/react` to stable `@lit/react`\n- Updated Bootstrap Icons to 1.11.1\n- Updated Lit to 3.0.0\n- Updated TypeScript to 5.2.2\n- Updated all other dependencies to latest versions\n\n## 2.9.0\n\n- Added the `modal` property to `<sl-dialog>` and `<sl-drawer>` to support third-party modals [#1571]\n- Fixed a bug in the autoloader causing it to register non-Shoelace elements [#1563]\n- Fixed a bug in `<sl-switch>` that resulted in improper spacing between the label and the required asterisk [#1540]\n- Fixed a bug in `<sl-icon>` that caused icons to not load when the default library used a sprite sheet [#1572]\n- Removed error when a missing popup anchor is provided [#1548]\n- Updated `@ctrl/tinycolor` to 4.0.1 [#1542]\n- Updated Bootstrap Icons to 1.11.0\n\n## 2.8.0\n\n- Added `--isolatedModules` and `--verbatimModuleSyntax` to `tsconfig.json`. For anyone directly importing event types, they no longer provide a default export due to these options being enabled. For people using the `events/event.js` file directly, there is no change.\n- Added support for submenus in `<sl-menu-item>` [#1410]\n- Added the `--submenu-offset` custom property to `<sl-menu-item>` [#1410]\n- Fixed an issue with focus trapping elements like `<sl-dialog>` when wrapped by other elements not checking the assigned elements of `<slot>`s [#1537]\n- Fixed type issues with the `ref` attribute in React Wrappers [#1526]\n- Fixed a regression that caused `<sl-radio-button>` to render incorrectly with gaps [#1523]\n- Improved expand/collapse behavior of `<sl-tree>` to work more like users expect [#1521]\n- Improved `<sl-menu-item>` so labels truncate properly instead of getting chopped and overflowing\n- Removed the extra `React.Component` around `@lit-labs/react` wrapper [#1531]\n- Updated `@lit-labs/react` to v2.0.1 [#1531]\n\n## 2.7.0\n\n- Added the experimental `<sl-copy-button>` component [#1473]\n- Fixed a bug in `<sl-dropdown>` where pressing [[Up]] or [[Down]] when focused on the trigger wouldn't focus the first/last menu items [#1472]\n- Fixed a bug that caused key presses in text fields to be hijacked when used inside `<sl-tree>` [#1492]\n- Fixed an upstream bug that caused React CodePen examples to stop working\n- Improved the behavior of the clear button in `<sl-input>` to prevent the component's width from shifting when toggled [#1496]\n- Improved `<sl-tooltip>` to prevent user selection so the tooltip doesn't get highlighted when dragging selections\n- Moved tag type definitions out of component files and into definition files\n- Removed `sideEffects` key from `package.json`. Update React docs to use cherry-picking [#1485]\n- Updated Bootstrap Icons to 1.10.5\n\n## 2.6.0\n\n- Added JSDoc comments to React Wrappers for better documentation when hovering a component [#1450]\n- Added `displayName` to React Wrappers for better debugging [#1450]\n- Added non-auto-registering routes for Components to fix a number of issues around auto-registration [#1450]\n- Added a console warning if you attempt to register the same Shoelace component twice [#1450]\n- Added tests for `<sl-qr-code>` [#1416]\n- Added support for pressing [[Space]] to select/toggle selected `<sl-menu-item>` elements [#1429]\n- Added support for virtual elements in `<sl-popup>` [#1449]\n- Added the `spinner` part to `<sl-button>` [#1460]\n- Added a `shoelace.js` and `shoelace-autoloader.js` to exportmaps [#1450]\n- Added types to events emitted by React wrapped components [#1419]\n- Fixed React component treeshaking by introducing `sideEffects` key in `package.json` [#1450]\n- Fixed a bug in `<sl-tree>` where it was auto-defining `<sl-tree-item>` [#1450]\n- Fixed a bug in focus trapping of modal elements like `<sl-dialog>`. We now manually handle focus ordering as well as added `offsetParent()` check for tabbable boundaries in Safari. Test cases added for `<sl-dialog>` inside a shadowRoot [#1403]\n- Fixed a bug in `valueAsDate` on `<sl-input>` where it would always set `type=\"date\"` for the underlying `<input>` element. It now falls back to the native browser implementation for the in-memory input. This may cause unexpected behavior if you're using `valueAsDate` on any input elements that aren't `type=\"date\"` [#1399]\n- Fixed a bug in `<sl-qr-code>` where the `background` attribute was never passed to the QR code [#1416]\n- Fixed a bug in `<sl-dropdown>` where aria attributes were incorrectly applied to the default `<slot>` causing Lighthouse errors [#1417]\n- Fixed a bug in `<sl-carousel>` that caused navigation to work incorrectly in some case [#1420]\n- Fixed a number of slots that incorrectly had aria- and/or role attributes directly on them [#1422]\n- Fixed a bug in `<sl-tree>` that caused focus to be stolen when removing focused tree items [#1430]\n- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` that caused nested modals to respond too eagerly to the [[Esc]] key [#1457]\n- Improved `<sl-details>` to use `<details>` internally for better semantics and to enable search to find in supportive browsers when collapsed [#1470]\n- Updated ESLint and related plugins to the latest versions\n- Changed the default entrypoint for jsDelivr to point to the autoloader [#1450]\n\n## 2.5.2\n\n- Fixed broken source buttons in the docs [#1401]\n\n## 2.5.1\n\n- Fixed missing extensions from imports that broke with TypeScript 5 [#1391]\n- Fixed a regression that caused slotted styles to not work in `<sl-select>` [#1387]\n- Reimplemented the theme switcher so it supports light, dark, and system (auto) in the docs [#1395]\n\n## 2.5.0\n\nThis release [unbundles Lit](https://github.com/shoelace-style/shoelace/issues/559) (and other dependencies) from Shoelace. There are now two distributions for the project:\n\n1. `cdn/` – a bundled, CDN-ready distribution\n2. `dist/` – an unbundled, npm-ready distribution\n\n:::warning\nIf you're a CDN user, you must update your path to point to `cdn/` instead of `dist/`. You can copy and paste the latest paths from the [installation page](/getting-started/installation).\n:::\n\n- Added a `cdn/` distribution for bundled dependencies (imports for npm users remain the same) [#1369]\n- Added the `checkbox` part and related exported parts to `<sl-tree-item>` so you can target it with CSS [#1318]\n- Added the `submenu-icon` part to `<sl-menu-item>` (submenus have not been implemented yet, but this part is required to allow customizations)\n- Added the ability to use Sprite Sheets when using `<sl-icon>` via a custom resolver.\n- Added tests for `<sl-split-panel>` [#1343]\n- Fixed a bug where changing the size of `<sl-radio-group>` wouldn't update the size of child elements\n- Fixed a bug in `<sl-select>` and `<sl-color-picker>` where the `size` attribute wasn't being reflected [#1318]\n- Fixed a bug in `<sl-radio-group>` where `<sl-radio>` would not get checked if `<sl-radio-group>` was defined first [#1364]\n- Fixed a bug in `<sl-input>` that caused date pickers to look filled in even when empty in Safari [#1341]\n- Fixed a bug in `<sl-radio-group>` that sometimes caused dual scrollbars in containers that overflowed [#1380]\n- Fixed a bug in `<sl-carousel>` not loading the English language pack automatically [#1384]\n- Improved `<sl-button>` so it can accept children of variable heights [#1317]\n- Improved the docs to more clearly explain sizing radios and radio buttons\n- Improved the performance of `<sl-rating>` by partially rendering unseen icons [#1310]\n- Improved the Portuguese translation [#1336]\n- Improved the German translation [#1339]\n- Improved the autoloader so it watches `<html>` instead of `<body>` since the latter gets replaced by some frameworks [#1338]\n- Improved the Rails documentation [#1258]\n- Replaced Docsify with Eleventy to generate a static HTML version of the docs\n- Updated esbuild to 0.18.2\n- Updated Lit to 2.7.5\n- Updated TypeScript to 5.1.3\n\n## 2.4.0\n\n- Added the `discover()` function to the experimental autoloader's exports [#1236]\n- Added the `size` attribute to `<sl-radio-group>` so labels and controls will be sized consistently [#1301]\n- Added tests for `<sl-animated-image>` [#1246]\n- Added tests for `<sl-animation>` [#1274]\n- Fixed a bug in `<sl-tree-item>` that prevented long labels from wrapping [#1243]\n- Fixed a bug in `<sl-tree-item>` that caused labels to be misaligned when text wraps [#1244]\n- Fixed an incorrect CSS property value in `<sl-checkbox>` [#1272]\n- Fixed a bug in `<sl-avatar>` that caused the initials to show up behind images with transparency [#1260]\n- Fixed a bug in `<sl-split-panel>` that prevented the divider from being focusable in some browsers [#1288]\n- Fixed a bug that caused `<sl-tab-group>` to affect scrolling when initializing [#1292]\n- Fixed a bug in `<sl-menu-item>` that allowed the hover state to show when focused [#1282]\n- Fixed a bug in `<sl-carousel>` that prevented interactive elements from receiving clicks [#1262]\n- Fixed a bug in `<sl-input>` that caused `valueAsDate` and `valueAsNumber` to not be set synchronously in some cases [#1302]\n- Improved the behavior of `<sl-carousel>` when used inside a flex container [#1235]\n- Improved the behavior of `<sl-tree-item>` to support buttons and other interactive elements [#1234]\n- Improved the performance of `<sl-include>` to prevent an apparent memory leak in some browsers [#1284]\n- Improved the accessibility of `<sl-select>`, `<sl-split-panel>`, and `<sl-details>` by ensuring slots don't have roles [#1287]\n\n## 2.3.0\n\n- Added an experimental autoloader\n- Added the `subpath` argument to `getBasePath()` to make it easier to generate full paths to any file\n- Added `custom-elements.json` to package exports\n- Added `tag__base`, `tag__content`, `tag__remove-button`, `tag__remove-button__base` parts to `<sl-select>`\n- Fixed a bug in `<sl-rating>` that allowed the `sl-change` event to be emitted when disabled [#1220]\n- Fixed a regression in `<sl-input>` that caused `min` and `max` to stop working when `type=\"date\"` [#1224]\n- Improved accessibility of `<sl-carousel>` [#1218]\n- Improved `<sl-option>` so it converts non-string values to strings for convenience [#1226]\n- Updated the docs to dogfood the autoloader\n\n## 2.2.0\n\n- Added TypeScript types to all custom events [#1183]\n- Added the `svg` part to `<sl-icon>`\n- Added the `getForm()` method to all form controls [#1180]\n- Added the experimental carousel component [#851]\n- Fixed a bug in `<sl-select>` that caused the display label to render incorrectly in Chrome after form validation [#1197]\n- Fixed a bug in `<sl-input>` that prevented users from applying their own value for `autocapitalize`, `autocomplete`, and `autocorrect` when using `type=\"password` [#1205]\n- Fixed a bug in `<sl-tab-group>` that prevented scroll controls from showing when dynamically adding tabs [#1208]\n- Fixed a bug in `<sl-input>` that caused the calendar icon to be clipped in Firefox [#1213]\n- Fixed a bug in `<sl-tab>` that caused `sl-tab-show` to be emitted when activating the close button\n- Fixed a bug in `<sl-spinner>` that caused `--track-color` to be invisible with certain colors\n- Fixed a bug in `<sl-menu-item>` that caused the focus color to show when selecting menu items with a mouse or touch device\n- Fixed a bug in `<sl-select>` that caused `sl-change` and `sl-input` to be emitted too early [#1201]\n- Fixed a positioning edge case that caused `<sl-popup>` to positioned nested popups incorrectly [#1135]\n- Fixed a bug in `<sl-tree>` that caused the tree item to collapse when clicking a child item, dragging the mouse, and releasing it on the parent node [#1082]\n- Updated `@shoelace-style/localize` to 3.1.0\n- Updated `@floating-ui/dom` to 1.2.1\n\nWhen using `<input type=\"password\">` the default value for `autocapitalize`, `autocomplete`, and `autocorrect` may be affected due to the bug fixed in [#1205]restore the previous behavior.\n\n## 2.1.0\n\n- Added the `sl-focus` and `sl-blur` events to `<sl-color-picker>`\n- Added the `focus()` and `blur()` methods to `<sl-color-picker>`\n- Added the `sl-invalid` event to all form controls to enable custom validation logic [#1167]\n- Added `validity` and `validationMessage` properties to all form controls [#1167]\n- Added the `rel` attribute to `<sl-button>` to allow users to create button links that point to specific targets [#1200]\n- Fixed a bug in `<sl-animated-image>` where the play and pause buttons were transposed [#1147]\n- Fixed a bug that prevented `web-types.json` from being generated [#1154]\n- Fixed a bug in `<sl-color-picker>` that prevented `sl-change` and `sl-input` from emitting when using the eye dropper [#1157]\n- Fixed a bug in `<sl-dropdown>` that prevented keyboard users from selecting menu items when using the keyboard [#1165]\n- Fixed a bug in the template for `<sl-select>` that caused the `form-control-help-text` part to not be in the same location as other form controls [#1178]\n- Fixed a bug in `<sl-checkbox>` and `<sl-switch>` that caused the browser to scroll incorrectly when focusing on a control in a container with overflow [#1169]\n- Fixed a bug in `<sl-menu-item>` that caused the `click` event to be emitted when the item was disabled [#1113]\n- Fixed a bug in form controls that erroneously prevented validation states from being set when `novalidate` was used on the containing form [#1164]\n- Fixed a bug in `<sl-checkbox>` that caused the required asterisk to appear before the label in Chrome\n- Fixed a bug that prevented large form control labels from having the correct font size [#1195]\n- Improved the behavior of `<sl-dropdown>` in Safari so keyboard interaction works the same as in other browsers [#1177]\n- Improved the [icons](/components/icon) page so it's not as sluggish in Safari [#1122]\n- Improved the accessibility of `<sl-switch>` when used in forced-colors / Windows High Contrast mode [#1114]\n- Improved user interaction heuristics for all form controls [#1175]\n\n## 2.0.0\n\nThis is the first stable release of Shoelace 2, meaning breaking changes to the API will no longer be accepted for this version. Development of Shoelace 2.0 started in January 2020. The first beta was released on [July 15, 2020](https://github.com/shoelace-style/shoelace/releases/tag/v2.0.0-beta.1). Since then, Shoelace has grown quite a bit! Here are some stats from the project as of January 24, 2023:\n\n- 55 components have been built\n- [Over 2,500 commits](https://github.com/shoelace-style/shoelace/commits/next) have been made to the project\n- [88 beta versions](https://github.com/shoelace-style/shoelace/tags) have been released\n- [85 people](https://github.com/shoelace-style/shoelace/graphs/contributors) have contributed to the project\n- [669 issues](https://github.com/shoelace-style/shoelace/issues?q=is%3Aissue+is%3Aclosed) have been filed on GitHub\n- [274 pull requests](https://github.com/shoelace-style/shoelace/pulls) have been opened\n- [More than 150 discussions](https://github.com/shoelace-style/shoelace/discussions) have been started on GitHub\n- [Over 500 people](https://discord.com/invite/mg8f26C) have joined the Shoelace community on Discord\n- [Over 300 million CDN hits](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace) per month\n- [Over 13,000 npm downloads](https://www.npmjs.com/package/@shoelace-style/shoelace) per week\n- [73rd most popular project](https://www.jsdelivr.com/statistics) on jsDelivr\n- [#2\n\nI'd like to extend a very special thank you to every single contributor who worked to make this possible. Everyone who's filed a bug, submitted a PR, requested a feature, started a discussion, helped with testing, and advocated for the project. You are just as responsible for Shoelace's success as I am. I'd also like to thank the folks at [Font&nbsp;Awesome](https://fontawesome.com/) for recognizing Shoelace's potential and [believing in me](https://blog.fontawesome.com/shoelace-joins-font-awesome/) to make it happen.\n\nThank you! And keep building _awesome_ stuff!\n\nWithout further ado, here are the notes for this release.\n\n- Added support for the `inert` attribute on `<sl-menu-item>` to allow hidden menu items to not accept focus [#1107]\n- Added the `tag` part to `<sl-select>`\n- Added `sl-hover` event to `<sl-rating>` [#1125]\n- Added the `@documentation` tag with a link to the docs for each component\n- Added the `form` attribute to all form controls to allow placing them outside of a `<form>` element [#1130]\n- Added the `getFormControls()` function as an alternative to `HTMLFormElement.elements`\n- Added missing docs for the `header-actions` slot in `<sl-dialog>` and `<sl-drawer>`\n- Added `hue-slider-handle` and `opacity-slider-handle` parts to `<sl-color-picker>` and correct other part names in the docs [#1142]\n- Fixed a bug in `<sl-select>` that prevented placeholders from showing when `multiple` was used [#1109]\n- Fixed a bug in `<sl-select>` that caused tags to not be rounded when using the `pill` attribute [#1117]\n- Fixed a bug in `<sl-select>` where the `sl-change` and `sl-input` events didn't weren't emitted when removing tags [#1119]\n- Fixed a bug in `<sl-select>` that caused the listbox to scroll to the first selected item when selecting multiple items [#1138]\n- Fixed a bug in `<sl-select>` where the input color and input hover color wasn't using the correct design tokens [#1143]\n- Fixed a bug in `<sl-color-picker>` that logged a console error when parsing swatches with whitespace\n- Fixed a bug in `<sl-color-picker>` that caused selected colors to be wrong due to incorrect HSV calculations\n- Fixed a bug in `<sl-color-picker>` that prevented the initial value from being set correct when assigned as a property [#1141]\n- Fixed a bug in `<sl-radio-button>` that caused the checked button's right border to be incorrect [#1110]\n- Fixed a bug in `<sl-spinner>` that caused the animation to stop working correctly in Safari [#1121]\n- Fixed a bug that prevented the entire `<sl-tab-panel>` to be hidden when inactive\n- Fixed a bug that caused the value of `<sl-radio-group>` to be `undefined` depending on where the radio was activated [#1134]\n- Fixed a bug that caused body content to shift when scroll locking was enabled [#1132]\n- Fixed a bug in `<sl-icon>` that caused icons to sometimes be clipped in Safari\n- Fixed a bug that prevented label colors from inheriting by default in `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>`\n- Fixed a bug in `<sl-radio-group>` that caused an extra margin between the host element and the internal fieldset [#1139]\n- Refactored the `ShoelaceFormControl` interface to remove the `invalid` property, allowing a more intuitive API for controlling validation internally\n- Renamed the internal `FormSubmitController` to `FormControlController` to better reflect what it's used for\n- Updated Lit to 2.6.1\n- Updated Floating UI to 1.1.0\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.88\n\nThis release includes a complete rewrite of `<sl-select>` to improve accessibility and simplify its internals.\n\n- 🚨 BREAKING: rewrote `<sl-select>`\n  - Accessibility has been significantly improved, especially in screen readers\n  - You must use `<sl-option>` instead of `<sl-menu-item>` for options now\n  - The `suffix` slot was removed because it was confusing to users and its position made the clear button inaccessible\n  - The `max-tags-visible` attribute has been renamed to `max-options-visible`\n  - Many parts have been removed or renamed (please see the docs for more details)\n- 🚨 BREAKING: removed the `sl-label-change` event from `<sl-menu-item>` (listen for `slotchange` instead)\n- 🚨 BREAKING: removed type to select logic from `<sl-menu>` (this was added specifically for `<sl-select>` which no longer uses `<sl-menu>`)\n- 🚨 BREAKING: swatches in `<sl-color-picker>` are no longer present by default (but you can set them using the `swatches` attribute now)\n- 🚨 BREAKING: improved the accessibility of `<sl-menu-item>` so checked items are announced as such\n  - Checkbox menu items must now have `type=\"checkbox\"` before applying the `checked` attribute\n  - Checkbox menu items will now toggle their `checked` state on their own when selected\n  - Disabled menu items will now receive focus, but are still not selectable\n- Added the `<sl-option>` component\n- Added Traditional Chinese translation [#1086]\n- Added support for `swatches` to be an attribute of `<sl-color-picker>` so swatches can be defined declaratively (it was previously a property; use a `;` to separate color values)\n- Fixed a bug in `<sl-tree-item>` where the checked/indeterminate states could get out of sync when using the `multiple` option [#1076]\n- Fixed a bug in `<sl-tree>` that caused `sl-selection-change` to emit before the DOM updated [#1096]\n- Fixed a bug that prevented `<sl-switch>` from submitting a default value of `on` when no value was provided [#1103]\n- Fixed a bug in `<sl-textarea>` that caused the scrollbar to show sometimes when using `resize=\"auto\"`\n- Fixed a bug in `<sl-input>` and `<sl-textarea>` that caused its validation states to be out of sync in some cases [#1063]\n- Reorganized all components to make class structures more consistent\n- Updated some incorrect default values for design tokens in the docs [#1097]\n- Updated non-public fields to use the `private` keyword (these were previously private only by convention, but now TypeScript will warn you)\n- Updated the hover style of `<sl-menu-item>` to be consistent with `<sl-option>`\n- Updated the status of `<sl-tree>` and `<sl-tree-item>` from experimental to stable\n- Updated React wrappers to use the latest API from `@lit-labs/react` [#1090]\n- Updated Bootstrap Icons to 1.10.3\n\n## 2.0.0-beta.87\n\n- 🚨 BREAKING: changed the default size of medium checkboxes, radios, and switches to 18px instead of 16px\n- 🚨 BREAKING: renamed the `--sl-toggle-size` design token to `--sl-toggle-size-medium`\n- Added the `--sl-toggle-size-small` and `--sl-toggle-size-large` design tokens\n- Added the `size` attribute to `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>` [#1071]\n- Added the `sl-input` event to `<sl-checkbox>`, `<sl-color-picker>`, `<sl-radio>`, `<sl-range>`, and `<sl-switch>`\n- Added HSV format to `<sl-color-picker>` [#1072]\n- Fixed a bug in `<sl-color-picker>` that sometimes prevented the color from updating when clicking or tapping on the controls\n- Fixed a bug in `<sl-color-picker>` that prevented text from being entered in the color input\n- Fixed a bug in `<sl-input>` that caused the `sl-change` event to be incorrectly emitted when the value was set programmatically [#917]\n- Fixed a bug in `<sl-input>` and `<sl-textarea>` that made it impossible to disable spell checking [#1061]\n- Fixed non-modal behaviors in `<sl-drawer>` when using the `contained` attribute [#1051]\n- Fixed a bug in `<sl-checkbox>` and `<sl-radio>` that caused the checked icons to not scale property when resized\n- Fixed a bug that broke React imports [#1050]\n- Refactored `<sl-color-picker>` to use `@ctrl/tinycolor` instead of `color` saving ~67KB [#1072]\n- Removed the `formdata` event polyfill since it's now available in the last two versions of all major browsers\n\n## 2.0.0-beta.86\n\n- 🚨 BREAKING: changed the default value of `date` in `<sl-relative-time>` to the current date instead of the Unix epoch\n- 🚨 BREAKING: removed the `handle-icon` part and slot from `<sl-image-comparer>` (use `handle` instead)\n- 🚨 BREAKING: removed the `handle` slot from `<sl-split-panel>` (use the `divider` slot instead)\n- 🚨 BREAKING: removed the `--box-shadow` custom property from `<sl-alert>` (apply a box shadow to `::part(base)` instead)\n- 🚨 BREAKING: removed the `play-icon` and `pause-icon` parts (use the `play-icon` and `pause-icon` slots instead)\n- Added `header-actions` slot to `<sl-dialog>` and `<sl-drawer>`\n- Added the `expand-icon` and `collapse-icon` slots to `<sl-details>` and refactored the icon animation [#1046]\n- Added the `play-icon` and `pause-icon` slots to `<sl-animated-image>` so you can customize the default icons\n- Converted `isTreeItem()` export to a static method of `<sl-tree-item>`\n- Fixed a bug in `<sl-tree-item>` where `sl-selection-change` was emitted when the selection didn't change [#1030]\n- Fixed a bug in `<sl-button-group>` that caused the border to render incorrectly when hovering over icons inside buttons [#1035]\n- Fixed an incorrect default for `flip-fallback-strategy` in `<sl-popup>` that caused the fallback strategy to be `initial` instead of `best-fit`, which is inconsistent with Floating UI's default [#1036]\n- Fixed a bug where browser validation tooltips would show up when hovering over form controls [#1037]\n- Fixed a bug in `<sl-tab-group>` that sometimes caused the active tab indicator to not animate\n- Fixed a bug in `<sl-tree-item>` that caused the expand/collapse icon slot to be out of sync when the node is open initially\n- Fixed the mislabeled `handle-icon` slot in `<sl-image-comparer>` (it now points to the `<slot>`, not the slot's fallback content)\n- Fixed the border radius in `<sl-dropdown>` so it matches with nested `<sl-menu>` elements\n- Fixed a bug that caused all button values to appear in submitted form data even if they weren't the submitter\n- Improved IntelliSense in VS Code, courtesy of [Burton's amazing CEM Analyzer plugin](https://github.com/break-stuff/cem-plugin-vs-code-custom-data-generator)\n- Improved accessibility of `<sl-alert>` so the alert is announced and the close button has a label\n- Improved accessibility of `<sl-progress-ring>` so slotted labels are announced along with visually hidden labels\n- Refactored all styles and animations to use `translate`, `rotate`, and `scale` instead of `transform`\n- Removed slot wrappers from many components, allowing better control over user-applied styles\n- Removed unused aria attributes from `<sl-skeleton>`\n- Replaced the `x` icon in the system icon library with `x-lg` to improve icon consistency\n\n## 2.0.0-beta.85\n\n- Fixed a bug in `<sl-dropdown>` that caused containing dialogs, drawers, etc. to close when pressing [[Escape]] while focused [#1024]\n- Fixed a bug in `<sl-tree-item>` that allowed lazy nodes to be incorrectly selected [#1023]\n- Fixed a typing bug in `<sl-tree-item>` [#1026]\n- Updated Floating UI to 1.0.7 to fix a bug that prevented `hoist` from working correctly in `<sl-dropdown>` after a recent update [#1024]\n\n## 2.0.0-beta.84\n\n- 🚨 BREAKING: Removed the `fieldset` property from `<sl-radio-group>` (use CSS parts if you want to keep the border) [#965]\n- 🚨 BREAKING: Removed `base` and `label` parts from `<sl-radio-group>` (use `form-control` and `form-control__label` instead) [#965]\n- 🚨 BREAKING: Removed the `base` part from `<sl-icon>` (style the host element directly instead)\n- 🚨 BREAKING: Removed the `invalid` attribute from form controls (use `[data-invalid]` to target it with CSS)\n- Added validation states to all form controls to allow styling based on various validation states [#1011]\n  - `data-required` - indicates that a value is required\n  - `data-optional` - indicates that a value is NOT required\n  - `data-invalid` - indicates that the form control is invalid\n  - `data-valid` - indicates that the form control is valid\n  - `data-user-invalid` - indicates the form control is invalid and the user has interacted with it\n  - `data-user-valid` - indicates the form control is valid and the user has interacted with it\n- Added npm exports [#1020]\n- Added `checkValidity()` method to all form controls\n- Added `reportValidity()` method to `<sl-range>`\n- Added `button--checked` to `<sl-radio-button>` and `control--checked` to `<sl-radio>` to style just the checked state [#933]\n- Added tests for `<sl-menu>`, `<sl-menu-item>`, `<sl-menu-label>`, `<sl-rating>`, `<sl-relative-time>`, `<sl-skeleton>`, `<sl-tab-panel>` and `<sl-tag>` [#935]\n  [#949]\n  [#956]\n- Added translations for Hungarian, Turkish, English (United Kingdom) and German (Austria) [#982]\n- Added `--indicator-transition-duration` custom property to `<sl-progress-ring>` [#986]\n- Added `--sl-input-required-content-color` custom property to all form controls [#948]\n- Added the ability to cancel `sl-show` and `sl-hide` events in `<sl-details>` [#993]\n- Added `focus()` and `blur()` methods to `<sl-radio-button>`\n- Added `stepUp()` and `stepDown()` methods to `<sl-input>` and `<sl-range>` [#1013]\n- Added `showPicker()` method to `<sl-input>` [#1013]\n- Added the `handle-icon` part to `<sl-image-comparer>`\n- Added `caret`, `check`, `grip-vertical`, `indeterminate`, and `radio` icons to the system library and removed `check-lg` [#985]\n- Added the `loading` attribute to `<sl-avatar>` to allow lazy loading of image avatars [#1006]\n- Added `formenctype` attribute to `<sl-button>` [#1009]\n- Added `exports` to `package.json` and removed the `main` and `module` properties [#1007]\n- Fixed a bug in `<sl-card>` that prevented the border radius to apply correctly to the header [#934]\n- Fixed a bug in `<sl-button-group>` where the inner border disappeared on focus [#980]\n- Fixed a bug that caused prefix/suffix animations in `<sl-input>` to wobble [#996]\n- Fixed a bug in `<sl-icon>` that prevented color from being set on the host element [#999]\n- Fixed a bug in `<sl-dropdown>` where the `keydown` event erroneously propagated to ancestors when pressing [[Escape]] [#990]\n- Fixed a bug that prevented arrow keys from scrolling content within `<sl-dialog>` and `<sl-drawer>` [#925]\n- Fixed a bug that prevented [[Escape]] from closing `<sl-dialog>` and `<sl-drawer>` in some cases\n- Fixed a bug that caused forms to submit unexpectedly when selecting certain characters [#988]\n- Fixed a bug in `<sl-radio-group>` that prevented the `invalid` property from correctly reflecting validity sometimes [#992]\n- Fixed a bug in `<sl-tree>` that prevented selections from working correctly on dynamically added tree items [#963]\n- Fixed module paths in `custom-elements.json` so they point to the dist file instead of the source file [#725]\n- Fixed an incorrect return value for `reportValidity()` in `<sl-color-picker>`\n- Improved `<sl-badge>` to improve padding and render relative to the current font size\n- Improved how many components display in forced-colors mode / Windows High Contrast mode\n  - Improved `<sl-color-picker>` so it's usable in forced-colors mode\n  - Improved `<sl-dialog>` and `<sl-drawer>` so the panel is more visible in forced-colors mode\n  - Improved `<sl-menu-item>` so selections are visible in forced-colors mode\n  - Improved `<sl-progress-bar>` so it's visible in forced-colors mode\n  - Improved `<sl-radio-button>` so checked states are visible in forced-colors mode\n  - Improved `<sl-range>` so the thumb, track, and tooltips are visible in forced-colors mode\n  - Improved `<sl-rating>` so icons are visible in forced-colors mode\n  - Improved `<sl-split-panel>` so the divider is visible in forced-colors mode\n  - Improved `<sl-tree-item>` so selected items are visible in forced-colors mode\n  - Improved `<sl-tab-group>` so tabs are cleaner and easier to understand in forced-colors mode\n- Improved positioning of the menu in `<sl-select>` so you can customize the menu width [#1018]\n- Moved all component descriptions to `@summary` to get them within documentation tools [#962]\n- Refactored form controls to use the `ShoelaceFormControl` interface to improve type safety and consistency\n- Updated Lit to 2.4.1\n- Updated `@shoelace-style/localize` t0 3.0.3 to support for extended language codes\n- Updated Bootstrap Icons to 1.10.2\n- Updated TypeScript to 4.8.4\n- Updated esbuild to 0.15.14\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.83\n\nThis release removes the `<sl-responsive-media>` component. When this component was introduced, support for [`aspect-ratio`](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)) wasn't great. These days, [the property is supported](https://caniuse.com/mdn-css_properties_aspect-ratio) by all of Shoelace's target browsers, making a dedicated component redundant.\n\n- 🚨 BREAKING: Removed `<sl-responsive-media>` (use the well-supported `aspect-ratio` CSS property instead)\n- 🚨 BREAKING: Changed the `toggle-password` attribute of `<sl-input>` to `password-toggle` for consistency\n- Added an expand/collapse animation to `<sl-tree-item>`\n- Added `sl-lazy-change` event to `<sl-tree-item>`\n- Added `expand-button` part to `<sl-tree-item>` [#893]\n- Added `password-visible` attribute to `<sl-input>` [#913]\n- Fixed a bug in `<sl-popup>` that didn't account for the arrow's diagonal size\n- Fixed a bug in `<sl-popup>` that caused arrow placement to be incorrect with RTL\n- Fixed a bug in `<sl-progress-ring>` that caused the indeterminate animation to stop working in Safari [#891]\n- Fixed a bug in `<sl-range>` that caused it to overflow a container at 100% width [#905]\n- Fixed a bug in `<sl-tree-item>` that prevented custom expand/collapse icons from rendering\n- Fixed a bug in `<sl-tree-item>` where the `expand-icon` and `collapse-icon` slots were reversed\n- Fixed a bug in `<sl-tree-item>` that prevented the keyboard from working after lazy loading [#882]\n- Fixed a bug in `<sl-textarea>` that prevented the textarea from resizing automatically when setting the value programmatically [#912]\n- Fixed a handful of paths to prevent TypeScript from getting upset [#886]\n- Fixed a bug in `<sl-radio-group>` where the `button-group__base` part was documented but not exposed [#909]\n- Fixed a bug in `<sl-range>` that caused the active track color to render on the wrong side in RTL [#916]\n- Refactored the internal event emitter to be part of `ShoelaceElement` to reduce imports and improve DX\n- Downgraded Floating UI from 1.0.1 to 1.0.0 due to new logic that makes positioning much slower for certain components [#915]\n- Upgraded the status of `<sl-animated-image>`, `<sl-popup>`, and `<sl-split-panel>` from experimental to stable\n\n## 2.0.0-beta.82\n\n- Added the `sync` and `arrow-placement` attributes to `<sl-popup>`\n- Changed the `auto-size` attribute of the experimental `<sl-popup>` component so it accepts `horizontal`, `vertical`, and `both` instead of a boolean value\n- Changed the `flip-fallback-placement` attribute of the experimental `<sl-popup>` component to `flip-fallback-placements`\n- Changed the `flip-fallback-strategy` in the experimental `<sl-popup>` component to accept `best-fit` and `initial` instead of `bestFit` and `initialPlacement`\n- Fixed a bug in `<sl-dropdown>` that caused the panel to resize horizontally when the trigger is clipped by the viewport [#860]\n- Fixed a bug in `<sl-tree>` where dynamically changing slotted items wouldn't update the tree properly\n- Fixed a bug in `<sl-split-panel>` that caused the panel to stack when clicking on the divider in mobile versions of Chrome [#862]\n- Fixed a bug in `<sl-popup>` that prevented flip fallbacks from working as intended\n- Fixed a bug that caused concurrent animations to work incorrectly when the durations were different [#867]\n- Fixed a bug in `<sl-color-picker>` that caused the trigger and color preview to ignore opacity on first render [#869]\n- Fixed a bug in `<sl-tree>` that prevented the keyboard from working when the component was nested in a shadow root [#871]\n- Fixed a bug in `<sl-tab-group>` that prevented the keyboard from working when the component was nested in a shadow root [#872]\n- Fixed a bug in `<sl-tab>` that allowed disabled tabs to erroneously receive focus\n- Improved single selection in `<sl-tree>` so nodes expand and collapse and receive selection when clicking on the label\n- Renamed `expanded-icon` and `collapsed-icon` slots to `expand-icon` and `collapse-icon` in the experimental `<sl-tree>` and `<sl-tree-item>` components\n- Improved RTL support for `<sl-image-comparer>`\n- Refactored components to extend from `ShoelaceElement` to make `dir` and `lang` reactive properties in all components\n\n## 2.0.0-beta.81\n\n- 🚨 BREAKING: removed the `base` part from `<sl-menu>` and removed an unnecessary `<div>` that made styling more difficult\n- Added the `anchor` property to `<sl-popup>` to support external anchors\n- Added read-only custom properties `--auto-size-available-width` and `--auto-size-available-height` to `<sl-popup>` to improve support for overflowing popup content\n- Added `label` to `<sl-rating>` to improve accessibility for screen readers\n- Added the `base__popup` and `base__arrow` parts to `<sl-tooltip>` [#858]\n- Fixed a bug where auto-size wasn't being applied to `<sl-dropdown>` and `<sl-select>`\n- Fixed a bug in `<sl-popup>` that caused auto-size to kick in before flip\n- Fixed a bug in `<sl-popup>` that prevented the `arrow-padding` attribute from working as expected\n- Fixed a bug in `<sl-tooltip>` that prevented the popup from appearing with the correct z-index [#854]\n- Improved accessibility of `<sl-rating>` so keyboard nav works better and screen readers announce it properly\n- Improved accessibility of `<sl-spinner>` so screen readers no longer skip over it\n- Removed a user agent sniffing notice that appeared in Chrome [#855]\n- Removed the default hover effect in `<sl-tree-items>` to make selections more obvious\n- Updated Floating UI to 1.0.1\n- Updated esbuild to 0.15.1\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.80\n\nThis release breaks radio buttons, which is something that needed to happen to solve a longstanding accessibility issue where screen readers announced an incorrect number of radios, e.g. \"1 of 1\" instead of \"1 of 3.\" Many attempts to solve this without breaking the existing API were made, but none worked across the board. The new implementation upgrades `<sl-radio-group>` to serve as the \"form control\" while `<sl-radio>` and `<sl-radio-button>` serve as options within the form control.\n\nTo upgrade to this version, you will need to rework your radio controls by moving `name` up to the radio group. And instead of setting `checked` to select a specific radio, you can set `value` on the radio group and the checked item will update automatically.\n\n- 🚨 BREAKING: improved accessibility of `<sl-radio-group>`, `<sl-radio>`, and `<sl-radio-button>` so they announce properly in screen readers\n  - Added the `name` attribute to `<sl-radio-group>` and removed it from `<sl-radio>` and `<sl-radio-button>`\n  - Added the `value` attribute to `<sl-radio-group>` (use this to control which radio is checked)\n  - Added the `sl-change` event to `sl-radio-group`\n  - Added `setCustomValidity()` and `reportValidity()` to `<sl-radio-group>`\n  - Removed the `checked` attribute from `<sl-radio>` and `<sl-radio-button>` (use the radio group's `value` attribute instead)\n  - Removed the `sl-change` event from `<sl-radio>` and `<sl-radio-button>` (listen for it on the radio group instead)\n  - Removed the `invalid` attribute from `<sl-radio>` and `<sl-radio-button>`\n  - Removed `setCustomValidity()` and `reportValidity()` from `<sl-radio>` and `<sl-radio-button>` (now available on the radio group)\n- Added the experimental `<sl-popup>` component\n- Fixed a bug in `<sl-menu-item>` where labels weren't always aligned correctly\n- Fixed a bug in `<sl-range>` that caused the tooltip to be positioned incorrectly when switching between LTR/RTL\n- Refactored `<sl-dropdown>` to use `<sl-popup>`\n- Refactored `<sl-tooltip>` to use `<sl-popup>` and added the `body` part\n- Revert disabled focus behavior in `<sl-tab-group>`, `<sl-menu>`, and `<sl-tree>` to be consistent with native form controls and menus [#845]\n\n## 2.0.0-beta.79\n\n- Added experimental `<sl-tree>` and `<sl-tree-item>` components [#823]\n- Added `--indicator-width` custom property to `<sl-progress-ring>` [#837]\n- Added Swedish translation [#838]\n- Added support for `step=\"any\"` for `<sl-input type=\"number\">` [#839]\n- Changed the type of component styles from `CSSResult` to `CSSResultGroup` [#828]\n- Fixed a bug in `<sl-color-picker>` where using [[Left]] and [[Right]] would select the wrong color\n- Fixed a bug in `<sl-dropdown>` that caused the position to be incorrect on the first show when using `hoist` [#843]\n- Fixed a bug in `<sl-tab-group>` where the divider was on the wrong side when using `placement=\"end\"`\n- Fixed a bug in `<sl-tab-group>` that caused nested tab groups to scroll when using `placement=\"start|end\"` [#815]\n- Fixed a bug in `<sl-tooltip>` that caused the target to be lost after a slot change [#831]\n- Fixed a bug in `<sl-tooltip>` that caused the position to be incorrect on the first show when using `hoist`\n- Improved accessibility of `<sl-tab-group>`, `<sl-tab>`, and `<sl-tab-panel>` to announce better in screen readers and by allowing focus on disabled items\n- Improved accessibility of `<sl-menu>` and `<sl-menu-item>` by allowing focus on disabled items\n- Updated Lit to 2.2.8\n- Update esbuild to 0.14.50\n- Updated Bootstrap Icons to 1.9.1\n- Updated Floating UI to 1.0.0\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.78\n\n- 🚨 BREAKING: Moved the `checked-icon` and `indeterminate-icon` parts from a wrapper `<span>` to the `<svg>` in `<sl-checkbox>` [#786]\n- 🚨 BREAKING: Moved the `checked-icon` part from a wrapper `<span>` to the `<svg>` in `<sl-radio>` [#786]\n- Added the `--track-active-offset` custom property to `<sl-range>` [#806]\n- Fixed a bug that caused `<sl-select>` to sometimes have two vertical scrollbars [#814]\n- Fixed a bug that caused a gray line to appear between radio buttons [#821]\n- Fixed a bug that caused `<sl-animated-image>` to not render anything when using the `play` attribute initially [#824]\n- Removed `:focus-visible` shim now that the last two major versions of Safari support it\n- Updated Bootstrap Icons to 1.9.0\n- Updated esbuild to 0.14.49\n- Updated Floating UI to 0.5.4\n- Updated Lit to 2.2.7\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.77\n\n- Added styles to required form controls so they show an asterisk next to the label by default\n- Added the `--sl-input-required-content` design token\n- Added the `required` attribute to `<sl-radio-group>` and fixed constraint validation logic to support custom validation\n- Added the `checked-icon` part to `<sl-menu-item>`\n- Added the `no-spin-buttons` attribute to `<sl-input type=\"number\">` [#798]\n- Added support for resetting forms using `<sl-button type=\"reset\">` [#799]\n- Fixed a bug where a duplicate clear button showed in Firefox [#791]\n- Fixed a bug where setting `valueAsDate` or `valueAsNumber` too early on `<sl-input>` would throw an error [#796]\n- Fixed a bug in `<sl-color-picker>` where empty values weren't properly supported [#797]\n- Fixed a bug in `<sl-color-picker>` where values were logged to the console when using the keyboard\n- Fixed a bug in `<sl-input>` where password controls would try to autocorrect/autocomplete/autocapitalize when the password is visible\n- Fixed label alignment in `<sl-checkbox>` and `<sl-radio>` so they align to the top of the control instead of the center when wrapping\n- Fixed labels in `<sl-checkbox>` and `<sl-radio>` so they use the `--sl-input-label-color` design token\n- Improved performance of the tabbable utility which can prevent the browser from temporarily locking up in focus traps [#800]\n- Updated the `fieldset` attribute so it reflects in `<sl-radio-group>`\n\n## 2.0.0-beta.76\n\n- Added support for RTL animations in the Animation Registry\n- Fixed a bug where the bottom border of `<sl-select>` could be cut off when the dropdown scrolls\n- Fixed a bug in `<sl-select>` that could result in the browser locking up due to an infinite positioning loop [#777]\n- Improved RTL animations for `<sl-drawer>` [#784]\n- Improved RTL styles for `<sl-button-group>` [#783]\n- Improved RTL styles for the toast stack [#785]\n- Improved typings for translations and localized terms\n- Upgraded @shoelace-style/localize to 3.0\n\n## 2.0.0-beta.75\n\n- Added Persian translation [#774]\n- Added `color-scheme` to light and dark themes to improve rendering of browser-provided UI [#776]\n- Added `--track-width` custom property to `<sl-tab-group>`\n- Fixed focus rings for `<sl-input>`, `<sl-select>`, and `<sl-textarea>` in Safari since they don't use `:focus-visible` [#767]\n- Fixed a bug where calling `HTMLFormElement.reportValidity()` would skip Shoelace form controls [#772]\n- Fixed a bug that prevented `<sl-tooltip>` from closing when disabled [#775]\n- Fixed a bug that allowed `<sl-icon-button>` to emit a `click` event when disabled [#781]\n- Improved the default icon for `<sl-image-comparer>` so it's more intuitive and removed `grip-vertical` from system icon library\n- Improved RTL styles for many components [#768]\n- Improved base path logic to execute only when `getBasePath()` is first called to better support SSR [#778]\n- Improved `DOMParser` instantiation in `<sl-icon>` to better support SSR [#778]\n- Reverted menu item caching due to regression [#766]\n- Updated Floating UI to 0.5.2\n\n## 2.0.0-beta.74\n\n- 🚨 BREAKING: reworked focus rings to use outlines instead of box shadows\n  - Removed the `--sl-focus-ring-alpha` design token\n  - Refactored `--sl-focus-ring` to be an `outline` property instead of a `box-shadow` property\n  - Added `--sl-focus-ring-color`, `--sl-focus-ring-style`, and `--sl-focus-ring-offset`\n- 🚨 BREAKING: removed `variant` from `<sl-radio-button>`\n- Added `sl-label-change` event to `<sl-menu-item>`\n- Added `blur()`, `click()`, and `focus()` methods as well as `sl-blur` and `sl-focus` events to `<sl-icon-button>` [#730]\n- Added Tabler Icons example to icons page\n- Fixed a bug where updating a menu item's label wouldn't update the display label in `<sl-select>` [#729]\n- Fixed a bug where the FormData event polyfill was causing issues with older browsers [#747]\n- Fixed a bug that caused a console error when setting `value` to `null` or `undefined` in `<sl-input>`, `<sl-select>`, and `<sl-textarea>` [#751]\n- Fixed a bug that caused `<sl-checkbox>` and `<sl-radio>` controls without a `value` to submit as `null` instead of `on` like native inputs [#744]\n- Fixed a bug that caused `<sl-dropdown>` and dependent components to add unexpected padding around the panel [#743]\n- Fixed a bug that prevented `valueAsDate` and `valueAsNumber` from updating synchronously [#760]\n- Fixed a bug that caused `<sl-menu-item>` to load icons from the default library instead of the system library [#765]\n- Fixed a bug in `<sl-input>` that prevented a canceled `keydown` event from submitting the containing form when pressing enter [#764]\n- Improved behavior of clearable and password toggle buttons in `<sl-input>` and `<sl-select>` [#745]\n- Improved performance of `<sl-select>` by caching menu items instead of traversing for them each time\n- Improved drag utility so initial click/touch events can be accepted [#758]\n- Improved `<sl-color-picker>` to use an HSB grid instead of HSL to be more consistent with existing color picker implementations [#762]\n- Improved `<sl-color-picker>` so the cursor is hidden and the preview is larger when dragging the grid\n- Refactored `<sl-menu>` to be more performant by caching menu items on slot change\n- Reverted form submit logic [#718]\n- Updated the `disabled` attribute so it reflects in `<sl-dropdown>` [#741]\n- Updated the `name` and `icon` attribute so they reflect in `<sl-icon>` [#742]\n- Updated Lit to 2.2.5\n- Updated Bootstrap Icons to 1.8.3\n- Updated TypeScript to 4.7.2\n- Updated esbuild to 0.14.40\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.73\n\n- Added `button` part to `<sl-radio-button>`\n- Added custom validity examples and tests to `<sl-checkbox>`, `<sl-radio>`, and `<sl-radio-button>`\n- Added `enterkeyhint` attribute to `<sl-input>` and `<sl-textarea>`\n- Fixed a bug that prevented `setCustomValidity()` from working with `<sl-radio-button>`\n- Fixed a bug where the right border of a checked `<sl-radio-button>` was the wrong color\n- Fixed a bug that prevented a number of properties, methods, etc. from being documented in `<sl-radio>` and `<sl-radio-button>`\n- Fixed a bug in `<sl-avatar>` that prevented valid images from showing after an invalid or missing image was provided [#717]\n- Fixed a bug that resulted in a console error being thrown on keydown in `<sl-dropdown>` [#719]\n- Fixed a bug that prevented `<sl-dropdown>` from being closed when opened initially [#720]\n- Fixed a bug that caused the test runner to fail when using a locale other than en-US [#726]\n- Improved form submit logic so most user-added event listeners will run after form data is attached and validation occurs [#718]\n- Improved accessibility of `<sl-tooltip>` so screen readers announce the content on hover/focus [#219]\n- Improved accessibility of form controls by exposing clear buttons and password visibility buttons to screen readers while keeping them out of the tab order [#727]\n- Updated `<sl-tab-group>` and `<sl-menu>` to cycle through tabs and menu items instead of stopping at the first/last when using the keyboard\n- Removed path aliasing (again) because it doesn't work with Web Test Runner's esbuild plugin\n\n## 2.0.0-beta.72\n\n- 🚨 BREAKING: refactored parts in `<sl-input>`, `<sl-range>`, `<sl-select>`, and `<sl-textarea>` to allow you to customize the label and help text position\n  - Added `form-control-input` part\n  - Renamed `label` to `form-control-label`\n  - Renamed `help-text` to `form-control-help-text`\n- 🚨 BREAKING: removed status from the `sl-error` event payload in `<sl-icon>`\n- Added the experimental `<sl-radio-button>` component\n- Added `button-group` and `button-group__base` parts to `<sl-radio-group>`\n- Added the `label` attribute and slot to `<sl-color-picker>` to improve accessibility with screen readers\n- Fixed a bug that prevented form submission from working as expected in some cases\n- Fixed a bug that prevented `<sl-split-panel>` from toggling `vertical` properly [#703]\n- Fixed a bug that prevented `<sl-color-picker>` from rendering a color initially [#704]\n- Fixed a bug that caused focus trapping to fail when used inside a shadow root [#709]\n- Improved accessibility throughout the docs\n- Improved accessibility of `<sl-dropdown>` so the trigger's expanded state is announced correctly\n- Improved accessibility of `<sl-format-date>` but rendering a `<time>` element instead of plain text\n- Improved accessibility of `<sl-select>` so disabled controls announce correct\n- Improved accessibility in `<sl-tag>` so remove buttons have labels\n- Refactored `<sl-radio>` to move selection logic into `<sl-radio-group>`\n- Updated slot detection logic so it ignores visually hidden elements\n- Upgraded the status of `<sl-visually-hidden>` from experimental to stable\n\n## 2.0.0-beta.71\n\n- 🚨 BREAKING: refactored exported parts to ensure composed components and their parts can be targeted via CSS\n  - Refactored the `eye-dropper-button` part and added `eye-dropper-button__base`, `eye-dropper-button__prefix`, `eye-dropper-button__label`, `eye-dropper-button__suffix`, and `eye-dropper-button__caret` parts to `<sl-color-picker>`\n  - Refactored the `format-button` part and added `format-button__base`, `format-button__prefix`, `format-button__label`, `format-button__suffix`, and `format-button__caret` parts to `<sl-color-picker>`\n  - Moved the `close-button` part in `<sl-alert>` to the internal `<sl-icon-button>` and removed the `<span>` that wrapped it\n  - Moved the `close-button` part in `<sl-dialog>` and `<sl-drawer>` to point to the host element and added the `close-button__base` part\n  - Renamed parts in `<sl-select>` from `tag-base` to `tag__base`, `tag-content` to `tag__content`, and `tag-remove-button` to `tag__remove-button`\n  - Moved the `close-button` part in `<sl-tab>` to the internal `<sl-icon-button>` and added the `close-button__base` part\n  - Moved the `scroll-button` part in `<sl-tab-group>` to the internal `<sl-icon-button>` and added the `scroll-button__base`, `scroll-button--start`, and `scroll-button--end` parts\n  - Moved the `remove-button` part in `<sl-tag>` to the internal `<sl-icon-button>` and added the `remove-button__base` part\n- 🚨 BREAKING: removed `checked-icon` part from `<sl-menu-item>` in preparation for parts refactor\n- 🚨 BREAKING: changed the `typeToSelect()` method's argument from `String` to `KeyboardEvent` in `<sl-menu>` to support more advanced key combinations\n- Added `form`, `formaction`, `formmethod`, `formnovalidate`, and `formtarget` attributes to `<sl-button>` [#699]\n- Added Prettier and ESLint to markdown files\n- Added background color and border to `<sl-menu>`\n- Added more tests for `<sl-input>`, `<sl-select>`, and `<sl-textarea>`\n- Fixed a bug that prevented forms from submitting when pressing [[Enter]] inside of an `<sl-input>` [#700]\n- Fixed a bug in `<sl-input>` that prevented the `valueAsDate` and `valueAsNumber` properties from working when set before the component was initialized\n- Fixed a bug in `<sl-dropdown>` where pressing [[Home]] or [[End]] wouldn't select the first or last menu items, respectively\n- Improved `autofocus` behavior in Safari for `<sl-dialog>` and `<sl-drawer>` [#693]\n- Improved type to select logic in `<sl-menu>` so it supports [[Backspace]] and gives users more time before resetting\n- Improved checkmark size and positioning in `<sl-menu-item>`\n- Improved accessibility in form controls that have help text so they're announced correctly in various screen readers\n- Removed feature detection for `focus({ preventScroll })` since it no longer works in Safari\n- Removed the `--sl-tooltip-arrow-start-end-offset` design token\n- Removed the `pattern` attribute from `<sl-textarea>` as it was documented incorrectly and never supported\n- Replaced Popper positioning dependency with Floating UI in `<sl-dropdown>` and `<sl-tooltip>`\n\n## 2.0.0-beta.70\n\n- Added `tag-base`, `tag-content`, and `tag-remove-button` parts to `<sl-select>` [#682]\n- Added support for focusing elements with `autofocus` when `<sl-dialog>` and `<sl-drawer>` open [#688]\n- Added the `placement` attribute to `<sl-select>` [#687]\n- Added Danish translation [#690]\n- Fixed a bug that allowed `<sl-dropdown>` to go into an incorrect state when activating the trigger while disabled [#684]\n- Fixed a bug where Safari would sometimes not focus after preventing `sl-initial-focus` [#688]\n- Fixed a bug where the active tab indicator in `<sl-tab-group>` would be misaligned when using disabled tabs [#695]\n- Improved the size of the remove button in `<sl-tag>`\n- Removed Google Analytics from the docs\n\n## 2.0.0-beta.69\n\n- Added `web-types.json` to improve the dev experience for WebStorm/PHPStorm users [#328]\n- Fixed a bug that caused an error when pressing up/down in `<sl-select>`\n- Fixed a bug that caused `<sl-details>` to not show when double clicking the summary while open [#662]\n- Fixed a bug that prevented the first/last menu item from receiving focus when pressing up/down in `<sl-dropdown>`\n- Fixed a bug that caused the active tab indicator in `<sl-tab-group>` to render incorrectly when used inside an element that animates [#671]\n- Fixed a bug that allowed values in `<sl-range>` to be invalid according to its `min|max|step` [#674]\n- Updated Lit to 2.1.4\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.68\n\n- Fixed path aliases in generated files so they're relative again [#669]\n\n## 2.0.0-beta.67\n\n- Fixed a TypeScript config regression introduced in [#647]\n\n## 2.0.0-beta.66\n\n- Attempted to fix a bug that prevented types from being generated in the build\n\n## 2.0.0-beta.65\n\n- 🚨 BREAKING: the `unit` property of `<sl-format-bytes>` has changed to `byte | bit` instead of `bytes | bits`\n- Added `display-label` part to `<sl-select>` [#650]\n- Added `--spacing` custom property to `<sl-divider>` [#664]\n- Added `event.detail.source` to the `sl-request-close` event in `<sl-dialog>` and `<sl-drawer>`\n- Fixed a bug that caused `<sl-progress-ring>` to render the wrong size when `--track-width` was increased [#656]\n- Fixed a bug that allowed `<sl-details>` to open and close when disabled using a screen reader [#658]\n- Fixed a bug in the FormData event polyfill that threw an error in some environments [#666]\n- Implemented stricter linting to improve consistency and reduce errors, which resulted in many small refactors throughout the codebase [#647]\n- Improved accessibility of `<sl-dialog>` and `<sl-drawer>` by making the title an `<h2>` and adding a label to the close button\n- Improved search results in the documentation\n- Refactored `<sl-format-byte>` to use `Intl.NumberFormat` so it supports localization\n- Refactored themes so utility styles are no longer injected as `<style>` elements to support stricter CSP rules [#571]\n- Restored the nicer animation on `<sl-spinner>` and verified it works in Safari\n- Updated Feather icon example to use Lucide [#657]\n- Updated minimum Node version to 14.17\n- Updated Lit to 2.1.2\n- Updated to Bootstrap Icons to 1.8.1\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.64\n\n- 🚨 BREAKING: removed `<sl-form>` because all form components submit with `<form>` now ([learn more](/getting-started/form-controls))\n- 🚨 BREAKING: changed `submit` attribute to `type=\"submit\"` on `<sl-button>`\n- 🚨 BREAKING: changed the `alt` attribute to `label` in `<sl-avatar>` for consistency with other components\n- Added `role=\"status\"` to `<sl-spinner>`\n- Added `valueAsDate` and `valueAsNumber` properties to `<sl-input>` [#570]\n- Added `start`, `end`, and `panel` parts to `<sl-split-panel>` [#639]\n- Fixed broken spinner animation in Safari [#633]\n- Fixed an a11y bug in `<sl-tooltip>` where `aria-describedby` referenced an id in the shadow root\n- Fixed a bug in `<sl-radio>` where tabbing didn't work properly in Firefox [#596]\n- Fixed a bug in `<sl-input>` where clicking the left/right edge of the control wouldn't focus it\n- Fixed a bug in `<sl-input>` where autofill had strange styles [#644]\n- Improved `<sl-spinner>` track color when used on various backgrounds\n- Improved a11y in `<sl-radio>` so VoiceOver announces radios properly in a radio group\n- Improved the API for the experimental `<sl-split-panel>` component by making `position` accept a percentage and adding the `position-in-pixels` attribute\n- Refactored `<sl-breadcrumb-item>`, `<sl-button>`, `<sl-card>`, `<sl-dialog>`, `<sl-drawer>`, `<sl-input>`, `<sl-range>`, `<sl-select>`, and `<sl-textarea>` to use a Reactive Controller for slot detection\n- Refactored internal id usage in `<sl-details>`, `<sl-dialog>`, `<sl-drawer>`, and `<sl-dropdown>`\n- Removed `position: relative` from the common component stylesheet\n- Updated Lit to 2.1.0\n- Updated all other dependencies to latest versions\n\n## 2.0.0-beta.63\n\n- 🚨 BREAKING: changed the `type` attribute to `variant` in `<sl-alert>`, `<sl-badge>`, `<sl-button>`, and `<sl-tag>` since it's more appropriate and to disambiguate from other `type` attributes\n- 🚨 BREAKING: removed `base` part from `<sl-divider>` to simplify the styling API\n- Added the experimental `<sl-split-panel>` component\n- Added `focus()` and `blur()` methods to `<sl-select>` [#625]\n- Fixed a bug where setting `tooltipFormatter` on `<sl-range>` in JSX causes React@experimental to error out\n- Fixed a bug where clicking on a slotted icon in `<sl-button>` wouldn't submit forms [#626]\n- Added the `sl-` prefix to generated ids for `<sl-tab>` and `<sl-tab-panel>`\n- Refactored `<sl-button>` to use Lit's static expressions to reduce code\n- Simplified `<sl-spinner>` animation\n\n## 2.0.0-beta.62\n\n- 🚨 BREAKING: changed the `locale` attribute to `lang` in `<sl-format-bytes>`, `<sl-format-date>`, `<sl-format-number>`, and `<sl-relative-time>` to be consistent with how localization is handled\n- Added localization support including translations for English, German, German (Switzerland), Spanish, French, Hebrew, Japanese, Dutch, Polish, Portuguese, and Russian translations [#419]\n- CodePen examples will now open in light or dark depending on your current preference\n- Fixed a bug where tag names weren't being generated in `vscode.html-custom-data.json` [#593]\n- Fixed a bug in `<sl-tooltip>` where the tooltip wouldn't reposition when content changed\n- Fixed a bug in `<sl-select>` where focusing on a filled control showed the wrong focus ring\n- Fixed a bug where setting `value` initially on `<sl-color-picker>` didn't work in React [#602]\n- Updated filled inputs to have the same appearance when focused\n- Updated `color` dependency from 3.1.3 to 4.0.2\n- Updated `<sl-format-bytes>`, `<sl-format-date>`, `<sl-format-number>`, and `<sl-relative-time>` to work like other localized components\n- Upgraded the status of `<sl-qr-code>` from experimental to stable\n- Updated to Bootstrap Icons to 1.7.2\n- Upgraded color to 4.1.0\n\n## 2.0.0-beta.61\n\nThis release improves the dark theme by shifting luminance in both directions, slightly condensing the spectrum. This results in richer colors in dark mode. It also reduces theme stylesheet sizes by eliminating superfluous gray palette variations.\n\nIn [beta.48](#_200-beta48), I introduced a change to color tokens that allowed you to access alpha values at the expense of a verbose, non-standard syntax. After considering feedback from the community, I've decided to revert this change so the `rgb()` function is no longer required. Many users reported never using it for alpha, and even more reported having trouble remembering to use `rgb()` and that it was causing more harm than good.\n\nFurthermore, both Safari and Firefox have implemented [`color-mix()`](<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix()>) behind a flag, so access to alpha channels and other capabilities are coming to the browser soon.\n\nIf you're using color tokens in your own stylesheet, simply remove the `rgb()` to update to this version.\n\n```css\n.your-styles {\n  /* change this */\n  color: rgb(var(--sl-color-primary-500));\n\n  /* to this */\n  color: var(--sl-color-primary-500);\n}\n```\n\nThank you for your help and patience with testing Shoelace. I promise, we're not far from a stable release!\n\n- 🚨 BREAKING: removed blue gray, cool gray, true gray, and warm gray color palettes\n- 🚨 BREAKING: removed `--sl-focus-ring-color`, and `--sl-focus-ring-alpha` (use `--sl-focus-ring` instead)\n- 🚨 BREAKING: removed `--sl-surface-base` and `--sl-surface-base-alt` tokens (use the neutral palette instead)\n- Added experimental `<sl-visually-hidden>` component\n- Added `clear-icon` slot to `<sl-select>` [#591]\n- Fixed a bug in `<sl-progress-bar>` where the label would show in the default slot\n- Improved the dark theme palette so colors are bolder and don't appear washed out\n- Improved a11y of `<sl-avatar>` by representing it as an image with an `alt` [#579]\n- Improved a11y of the scroll buttons in `<sl-tab-group>`\n- Improved a11y of the close button in `<sl-tab>`\n- Improved a11y of `<sl-tab-panel>` by removing an invalid `aria-selected` attribute [#579]\n- Improved a11y of `<sl-icon>` by not using a variation of the `name` attribute for labels (use the `label` attribute instead)\n- Moved `role` from the shadow root to the host element in `<sl-menu>`\n- Removed redundant `role=\"menu\"` in `<sl-dropdown>`\n- Slightly faster animations for showing and hiding `<sl-dropdown>`\n- Updated to Bootstrap Icons to 1.7.1\n- Upgraded the status of `<sl-mutation-observer>` from experimental to stable\n\n## 2.0.0-beta.60\n\n- Added React examples and CodePen links to all components\n- Changed the `attr` in experimental `<sl-mutation-observer>` to require `\"*\"` instead of `\"\"` to target all attributes\n- Fixed a bug in `<sl-progress-bar>` where the `label` attribute didn't set the label\n- Fixed a bug in `<sl-rating>` that caused disabled and readonly controls to transition on hover\n- The `panel` property of `<sl-tab>` is now reflected\n- The `name` property of `<sl-tab-panel>` is now reflected\n\n## 2.0.0-beta.59\n\n- Added React wrappers as first-class citizens\n- Added eye dropper to `<sl-color-picker>` when the browser supports the [EyeDropper API](https://wicg.github.io/eyedropper-api/)\n- Fixed a bug in `<sl-button-group>` where buttons groups with only one button would have an incorrect border radius\n- Improved the `<sl-color-picker>` trigger's border in dark mode\n- Switched clearable icon from `x-circle` to `x-circle-fill` to make it easier to recognize\n- Updated to Bootstrap Icons to 1.7.0\n- Updated to Lit 2.0.2\n\n## 2.0.0-beta.58\n\nThis version once again restores the bundled distribution because the unbundled + CDN approach is currently confusing and [not working properly](https://github.com/shoelace-style/shoelace/issues/559#issuecomment-949662331). Unbundling the few dependencies Shoelace has is still a goal of the project, but [this jsDelivr bug](https://github.com/jsdelivr/jsdelivr/issues/18337) needs to be resolved before we can achieve it.\n\nI sincerely apologize for the instability of the last few beta releases as a result of this effort.\n\n- Added experimental `<sl-animated-image>` component\n- Added `label` attribute to `<sl-progress-bar>` and `<sl-progress-ring>` to improve a11y\n- Fixed a bug where the tooltip would show briefly when clicking a disabled `<sl-range>`\n- Fixed a bug that caused a console error when `<sl-range>` was used\n- Fixed a bug where the `nav` part in `<sl-tab-group>` was on the incorrect element [#563]\n- Fixed a bug where non-integer aspect ratios were calculated incorrectly in `<sl-responsive-media>`\n- Fixed a bug in `<sl-range>` where setting `value` wouldn't update the active and inactive portion of the track [#572]\n- Reverted to publishing the bundled dist and removed `/+esm` links from the docs\n- Updated to Bootstrap Icons to 1.6.1\n\n## 2.0.0-beta.57\n\n- Fix CodePen links and CDN links\n\n## 2.0.0-beta.56\n\nThis release is the second attempt at unbundling dependencies. This will be a breaking change only if your configuration _does not_ support bare module specifiers. CDN users and bundler users will be unaffected, but note the URLs for modules on the CDN must have the `/+esm` now.\n\n- Added the `hoist` attribute to `<sl-tooltip>` [#564]\n- Unbundled dependencies and configured external imports to be packaged with bare module specifiers\n\n## 2.0.0-beta.55\n\n- Revert unbundling due to issues with the CDN not handling bare module specifiers as expected\n\n## 2.0.0-beta.54\n\nShoelace doesn't have a lot of dependencies, but this release unbundles most of them so you can potentially save some extra kilobytes. This will be a breaking change only if your configuration _does not_ support bare module specifiers. CDN users and bundler users will be unaffected.\n\n- 🚨 BREAKING: renamed the `sl-clear` event to `sl-remove`, the `clear-button` part to `remove-button`, and the `clearable` property to `removable` in `<sl-tag>`\n- Added the `disabled` attribute to `<sl-resize-observer>`\n- Fixed a bug in `<sl-mutation-observer>` where setting `disabled` initially didn't work\n- Unbundled dependencies and configured external imports to be packaged with bare module specifiers\n\n## 2.0.0-beta.53\n\n- 🚨 BREAKING: removed `<sl-menu-divider>` (use `<sl-divider>` instead)\n- 🚨 BREAKING: removed `percentage` attribute from `<sl-progress-bar>` and `<sl-progress-ring>` (use `value` instead)\n- 🚨 BREAKING: switched the default `type` of `<sl-tag>` from `primary` to `neutral`\n- Added the experimental `<sl-mutation-observer>` component\n- Added the `<sl-divider>` component\n- Added `--sl-color-neutral-0` and `--sl-color-neutral-50` as early surface tokens to improve the appearance of alert, card, and panels in dark mode\n- Added the `--sl-panel-border-width` design token\n- Added missing background color to `<sl-details>`\n- Added the `--padding` custom property to `<sl-tab-panel>`\n- Added the `outline` variation to `<sl-button>` [#522]\n- Added the `filled` variation to `<sl-input>`, `<sl-textarea>`, and `<sl-select>` and supporting design tokens\n- Added the `control` part to `<sl-select>` so you can target the main control with CSS [#538]\n- Added a border to `<sl-badge>` to improve contrast when drawn on various background colors\n- Added `--track-color-active` and `--track-color-inactive` custom properties to `<sl-range>` [#550]\n- Added the undocumented custom properties `--thumb-size`, `--tooltip-offset`, `--track-height` on `<sl-range>`\n- Changed the default `distance` in `<sl-dropdown>` from `2` to `0` [#538]\n- Fixed a bug where `<sl-select>` would be larger than the viewport when it had lots of options [#544]\n- Fixed a bug where `<sl-progress-ring>` wouldn't animate in Safari\n- Updated the default height of `<sl-progress-bar>` from `16px` to `1rem` and added a subtle shadow to indicate depth\n- Removed the `lit-html` dependency and moved corresponding imports to `lit` [#546]\n\n## 2.0.0-beta.52\n\n- 🚨 BREAKING: changed the `--stroke-width` custom property of `<sl-spinner>` to `--track-width` for consistency\n- 🚨 BREAKING: removed the `size` and `stroke-width` attributes from `<sl-progress-ring>` so it's fully customizable with CSS (use the `--size` and `--track-width` custom properties instead)\n- Added the `--speed` custom property to `<sl-spinner>`\n- Added the `--size` and `--track-width` custom properties to `<sl-progress-ring>`\n- Added tests for `<sl-badge>` [#530]\n- Fixed a bug where `<sl-tab>` wasn't using a border radius token [#523]\n- Fixed a bug in the Remix Icons example where some icons would 404 [#528]\n- Updated `<sl-progress-ring>` to use only CSS for styling\n- Updated `<sl-spinner>` to use an SVG and improved the indicator animation\n- Updated to Lit 2.0 and lit-html 2.0 🔥\n\n## 2.0.0-beta.51\n\nA number of users had trouble counting characters that repeat, so this release improves design token patterns so that \"t-shirt sizes\" are more accessible. For example, `--sl-font-size-xxx-large` has become `--sl-font-size-3x-large`. This change applies to all design tokens that use this scale.\n\n- 🚨 BREAKING: all t-shirt size design tokens now use `2x`, `3x`, `4x` instead of `xx`, `xxx`, `xxxx`\n- Added missing `--sl-focus-ring-*` tokens to dark theme\n- Added an \"Importing\" section to all components with copy/paste code to make cherry picking easier\n- Improved the documentation search with a custom plugin powered by [Lunr](https://lunrjs.com/)\n- Improved the `--sl-shadow-x-small` elevation\n- Improved visibility of elevations and overlays in dark theme\n- Reduced the size of `<sl-color-picker>` slightly to better accommodate mobile devices\n- Removed `<sl-icon>` dependency from `<sl-color-picker>` and improved the copy animation\n\n## 2.0.0-beta.50\n\n- Added `<sl-breadcrumb>` and `<sl-breadcrumb-item>` components\n- Added `--sl-letter-spacing-denser`, `--sl-letter-spacing-looser`, `--sl-line-height-denser`, and `--sl-line-height-looser` design tokens\n- Fixed a bug where form controls would error out when the value was set to `undefined` [#513]\n\n## 2.0.0-beta.49\n\nThis release changes the way focus states are applied to elements. In browsers that support [`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible), it will be used. In unsupportive browsers ([currently only Safari](https://caniuse.com/mdn-css_selectors_focus-visible)), `:focus` will be used instead. This means the browser will determine whether a focus ring should be shown based on how the user interacts with the page.\n\nThis release also fixes a critical bug in the color scale where `--sl-color-neutral-0` and `--sl-color-neutral-1000` were reversed.\n\n- 🚨 BREAKING: fixed a bug where `--sl-color-neutral-0` and `--sl-color-neutral-1000` were inverted (swap them to update)\n- 🚨 BREAKING: removed the `no-fieldset` property from `<sl-radio-group>` (fieldsets are now hidden by default; use `fieldset` to show them)\n- 🚨 BREAKING: removed `--focus-ring` custom property from `<sl-input>`, `<sl-select>`, `<sl-tab>` for consistency with other form controls\n- Added `--swatch-size` custom property to `<sl-color-picker>`\n- Added `date` to `<sl-input>` as a supported `type`\n- Added the `--sl-focus-ring` design token for a more convenient way to apply focus ring styles\n- Adjusted elevation tokens to use neutral in light mode and black in dark mode\n- Adjusted `--sl-overlay-background-color` in dark theme to be black instead of gray\n- Fixed a bug in `<sl-color-picker>` where the opacity slider wasn't showing the current color\n- Fixed a bug where Edge in Windows would show the native password toggle next to the custom password toggle [#508]\n- Fixed a bug where pressing up/down in `<sl-tab-group>` didn't select the next/previous tab in vertical placements\n- Improved size of `<sl-color-picker>`\n- Improved icon contrast in `<sl-input>`\n- Improved contrast of `<sl-switch>`\n- Improved `:focus-visible` behavior in many components\n- Removed elevation from `<sl-color-picker>` when rendered inline\n- Removed custom `:focus-visible` logic in favor of a directive that outputs `:focus-visible` or `:focus` depending on browser support\n- Updated to Lit 2.0.0-rc.3\n- Updated to lit-html 2.0.0-rc.4\n\n## 2.0.0-beta.48\n\nThis release improves theming by offering both light and dark themes that can be used autonomously. It also improves contrast in most components, adds a variety of new color primitives, and changes the way color tokens are consumed.\n\nPreviously, color tokens were in hexadecimal format. Now, Shoelace now uses an `R G B` format that requires you to use the `rgb()` function in your CSS.\n\n```css\n.example {\n  /* rgb() is required now */\n  color: var(--sl-color-neutral-500);\n}\n```\n\nThis is more verbose than previous versions, but it has the advantage of letting you set the alpha channel of any color token.\n\n```css\n.example-with-alpha {\n  /* easily adjust opacity for any color token */\n  color: rgb(var(--sl-color-neutral-500) / 50%);\n}\n```\n\nThis change applies to all design tokens that implement a color. Refer to the [color tokens](/tokens/color) page for more details.\n\n- 🚨 BREAKING: all design tokens that implement colors have been converted to `R G B` and must be used with the `rgb()` function\n- 🚨 BREAKING: removed `--sl-color-black|white` color tokens (use `--sl-color-neutral-0|1000` instead)\n- 🚨 BREAKING: removed `--sl-color-primary|success|warning|info|danger-text` design tokens (use theme or primitive colors instead)\n- 🚨 BREAKING: removed `info` variant from `<sl-alert>`, `<sl-badge>`, `<sl-button>`, and `<sl-tag>` (use `neutral` instead)\n- 🚨 BREAKING: removed `--sl-color-info-*` design token (use `--sl-color-neutral-*` instead)\n- 🚨 BREAKING: renamed `dist/themes/base.css` to `dist/themes/light.css`\n- 🚨 BREAKING: removed `--sl-focus-ring-color-primary` tokens (use color tokens and `--sl-focus-ring-width|alpha` instead)\n- 🚨 BREAKING: removed `--tabs-border-color` from `<sl-tab-group>` (use `--track-color` instead)\n- 🚨 BREAKING: changed the default value for `effect` to `none` in `<sl-skeleton>` (use `sheen` to restore the original behavior)\n- Added new color primitives to the base set of design tokens\n- Added `--sl-color-*-950` swatches to all color palettes\n- Added a console error that appears when menu items have duplicate values in `<sl-select>`\n- Added CodePen link to code examples\n- Added `prefix` and `suffix` slots to `<sl-select>` [#501]\n- Added `--indicator-color` custom property to `<sl-tab-group>`\n- Exposed base and dark stylesheets so they can be imported via JavaScript [#438]\n- Fixed a bug in `<sl-menu>` where pressing [[Enter]] after using type to select would result in the wrong value\n- Fixed a bug in `<sl-radio-group>` where clicking a radio button would cause the wrong control to be focused\n- Fixed a bug in `<sl-button>` and `<sl-icon-button>` where an unintended `ref` attribute was present\n- Fixed a bug in the focus-visible utility that failed to respond to mouseup events\n- Fixed a bug where clicking on a menu item would persist its hover/focus state\n- Fixed a bug in `<sl-select>` where it would erroneously intercept important keyboard shortcuts [#504]\n- Improved contrast throughout all components [#128]\n- Refactored thumb position logic in `<sl-switch>` [#490]\n- Reworked the dark theme to use an inverted + shifted design token approach instead of component-specific selectors\n\n## 2.0.0-beta.47\n\nThis release improves how component dependencies are imported. If you've been cherry picking, you no longer need to import component dependencies manually. This significantly improves developer experience, making Shoelace even easier to use. For transparency, component dependencies will continue to be listed in the docs.\n\n- Added \"Reflects\" column to the properties table\n- Dependencies are now automatically imported for all components\n- Fixed a bug where tabbing into `<sl-radio-group>` would not always focus the checked radio\n- Fixed a bug in component styles that prevented the box sizing reset from being applied\n- Fixed a regression in `<sl-color-picker>` where dragging the grid handle wasn't smooth\n- Fixed a bug where slot detection could incorrectly match against slots of child elements [#481]\n- Fixed a bug in `<sl-input>` where focus would move to the end of the input when typing in Safari [#480]\n- Improved base path utility logic\n\n## 2.0.0-beta.46\n\nThis release improves the developer experience of `<sl-animation>`. Previously, an animation was assumed to be playing unless the `pause` attribute was set. This behavior has been reversed and `pause` has been removed. Now, animations will not play until the new `play` attribute is applied.\n\nThis is a lot more intuitive and makes it easier to activate animations imperatively. In addition, the `play` attribute is automatically removed automatically when the animation finishes or cancels, making it easier to restart finite animations. Lastly, the animation's timing is now accessible through the new `currentTime` property instead of `getCurrentTime()` and `setCurrentTime()`.\n\nIn addition, Shoelace no longer uses Sass. Component styles now use Lit's template literal styles and theme files use pure CSS.\n\n- 🚨 BREAKING: removed the `pause` attribute from `<sl-animation>` (use `play` to start and stop the animation instead)\n- 🚨 BREAKING: removed `getCurrentTime()` and `setCurrentTime()` from `<sl-animation>` (use the `currentTime` property instead)\n- 🚨 BREAKING: removed the `close-on-select` attribute from `<sl-dropdown>` (use `stay-open-on-select` instead)\n- Added the `currentTime` property to `<sl-animation>` to control the current time without methods\n- Fixed a bug in `<sl-range>` where the tooltip wasn't showing in Safari [#477]\n- Fixed a bug in `<sl-menu>` where pressing [[Enter]] in a menu didn't work with click handlers\n- Reworked `<sl-menu>` and `<sl-menu-item>` to use a roving tab index and improve keyboard accessibility\n- Reworked tabbable logic to be more performant [#466]\n- Switched component stylesheets from Sass to Lit's template literal styles\n- Switched theme stylesheets from Sass to CSS\n\n## 2.0.0-beta.45\n\nThis release changes the way component metadata is generated. Previously, the project used TypeDoc to analyze components and generate a very large file with type data. The data was then parsed and converted to an easier-to-consume file called `metadata.json`. Alas, TypeDoc is expensive to run and the metadata format wasn't standard.\n\nThanks to an amazing effort by [Pascal Schilp](https://twitter.com/passle_), the world has a simpler, faster way to gather metadata using the [Custom Elements Manifest Analyzer](https://github.com/open-wc/custom-elements-manifest). Not only is this tool faster, but the data follows the evolving `custom-elements.json` format. This is exciting because a standard format for custom elements opens the door for many potential uses, including documentation generation, framework adapters, IDE integrations, third-party uses, and more. [Check out Pascal's great article](https://dev.to/open-wc/introducing-custom-elements-manifest-gkk) for more info about `custom-elements.json` and the new analyzer.\n\nThe docs have been updated to use the new `custom-elements.json` file. If you're relying on the old `metadata.json` file for any purpose, this will be a breaking change for you.\n\n- 🚨 BREAKING: removed the `sl-overlay-click` event from `<sl-dialog>` and `<sl-drawer>` (use `sl-request-close` instead) [#471]\n- 🚨 BREAKING: removed `metadata.json` (use `custom-elements.json` instead)\n- Added `custom-elements.json` for component metadata\n- Added `sl-request-close` event to `<sl-dialog>` and `<sl-drawer>`\n- Added `dialog.denyClose` and `drawer.denyClose` animations\n- Fixed a bug in `<sl-color-picker>` where setting `value` immediately wouldn't trigger an update\n- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` where setting `open` initially didn't set a focus trap\n- Fixed a bug that resulted in form controls having incorrect validity when `disabled` was initially set [#473]\n- Fixed a bug in the docs that caused the metadata file to be requested twice\n- Fixed a bug where tabbing out of a modal would cause the browser to lag [#466]\n- Updated the docs to use the new `custom-elements.json` for component metadata\n\n## 2.0.0-beta.44\n\n- 🚨 BREAKING: all `invalid` props on form controls now reflect validity before interaction [#455]\n- Allow `null` to be passed to disable animations in `setDefaultAnimation()` and `setAnimation()`\n- Converted build scripts to ESM\n- Fixed a bug in `<sl-checkbox>` where `invalid` did not update properly\n- Fixed a bug in `<sl-dropdown>` where a `keydown` listener wasn't cleaned up properly\n- Fixed a bug in `<sl-select>` where `sl-blur` was emitted prematurely [#456]\n- Fixed a bug in `<sl-select>` where no selection with `multiple` resulted in an incorrect value [#457]\n- Fixed a bug in `<sl-select>` where `sl-change` was emitted immediately after connecting to the DOM [#458]\n- Fixed a bug in `<sl-select>` where non-printable keys would cause the menu to open\n- Fixed a bug in `<sl-select>` where `invalid` was not always updated properly\n- Reworked the `@watch` decorator to use `update` instead of `updated` resulting in better performance and flexibility\n\n## 2.0.0-beta.43\n\n- Added `?` to optional arguments in methods tables in the docs\n- Added the `scrollPosition()` method to `<sl-textarea>` to get/set scroll position\n- Added initial tests for `<sl-dialog>`, `<sl-drawer>`, `<sl-dropdown>`, and `<sl-tooltip>`\n- Fixed a bug in `<sl-tab-group>` where scrollable tab icons were not displaying correctly\n- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` where preventing clicks on the overlay no longer worked as described [#452]\n- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` where setting initial focus no longer worked as described [#453]\n- Fixed a bug in `<sl-card>` where the `slotchange` listener wasn't attached correctly [#454]\n- Fixed lifecycle bugs in a number of components [#451]\n- Removed `fill: both` from internal animate utility so styles won't \"stick\" by default [#450]\n\n## 2.0.0-beta.42\n\nThis release addresses an issue with the `open` attribute no longer working in a number of components, as a result of the changes in beta.41. It also removes a small but controversial feature that complicated show/hide logic and led to a poor experience for developers and end users.\n\nThere are two ways to show/hide affected components: by calling `show() | hide()` and by toggling the `open` prop. Previously, it was possible to call `event.preventDefault()` in an `sl-show | sl-hide ` handler to stop the component from showing/hiding. The problem becomes obvious when you set `el.open = false`, the event gets canceled, and in the next cycle `el.open` has reverted to `true`. Not only is this unexpected, but it also doesn't play nicely with frameworks. Additionally, this made it impossible to await `show() | hide()` since there was a chance they'd never resolve.\n\nTechnical reasons aside, canceling these events seldom led to a good user experience, so the decision was made to no longer allow `sl-show | sl-hide` to be cancelable.\n\n- 🚨 BREAKING: `sl-show` and `sl-hide` events are no longer cancelable\n- Added Iconoir example to the icon docs\n- Added Web Test Runner\n- Added initial tests for `<sl-alert>` and `<sl-details>`\n- Changed the `cancelable` default to `false` for the internal `@event` decorator\n- Fixed a bug where toggling `open` stopped working in `<sl-alert>`, `<sl-dialog>`, `<sl-drawer>`, `<sl-dropdown>`, and `<sl-tooltip>`\n- Fixed a bug in `<sl-range>` where setting a value outside the default `min` or `max` would clamp the value [#448]\n- Fixed a bug in `<sl-dropdown>` where placement wouldn't adjust properly when shown [#447]\n- Fixed a bug in the internal `shimKeyframesHeightAuto` utility that caused `<sl-details>` to measure heights incorrectly [#445]\n- Fixed a number of imports that should have been type imports\n- Updated Lit to 2.0.0-rc.2\n- Updated esbuild to 0.12.4\n\n## 2.0.0-beta.41\n\nThis release changes how components animate. In previous versions, CSS transitions were used for most show/hide animations. Transitions are problematic due to the way `transitionend` works. This event fires once _per transition_, and it's impossible to know which transition to look for when users can customize any possible CSS property. Because of this, components previously required the `opacity` property to transition. If a user were to prevent `opacity` from transitioning, the component wouldn't hide properly and the `sl-after-show|hide` events would never emit.\n\nCSS animations, on the other hand, have a more reliable `animationend` event. Alas, `@keyframes` don't cascade and can't be injected into a shadow DOM via CSS, so there would be no good way to customize them.\n\nThe most elegant solution I found was to use the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API), which offers more control over animations at the expense of customizations being done in JavaScript. Fortunately, through the [Animation Registry](/getting-started/customizing#animations), you can customize animations globally and/or per component with a minimal amount of code.\n\n- 🚨 BREAKING: changed `left` and `right` placements to `start` and `end` in `<sl-drawer>`\n- 🚨 BREAKING: changed `left` and `right` placements to `start` and `end` in `<sl-tab-group>`\n- 🚨 BREAKING: removed `--hide-duration`, `--hide-timing-function`, `--show-duration`, and `--show-timing-function` custom properties from `<sl-tooltip>` (use the Animation Registry instead)\n- Added the Animation Registry\n- Fixed a bug where removing `<sl-dropdown>` from the DOM and adding it back destroyed the popover reference [#443]\n- Updated animations for `<sl-alert>`, `<sl-dialog>`, `<sl-drawer>`, `<sl-dropdown>`, and `<sl-tooltip>` to use the Animation Registry instead of CSS transitions\n- Improved a11y by respecting `prefers-reduced-motion` for all show/hide animations\n- Improved `--show-delay` and `--hide-delay` behavior in `<sl-tooltip>` so they only apply on hover\n- Removed the internal popover utility\n\n## 2.0.0-beta.40\n\n- 🚨 BREAKING: renamed `<sl-responsive-embed>` to `<sl-responsive-media>` and added support for images and videos [#436]\n- Fixed a bug where setting properties before an element was defined would render incorrectly [#425]\n- Fixed a bug that caused all modules to be imported when cherry picking certain components [#439]\n- Fixed a bug where the scrollbar would reposition `<sl-dialog>` on hide causing it to jump [#424]\n- Fixed a bug that prevented the project from being built in a Windows environment\n- Improved a11y in `<sl-progress-ring>`\n- Removed `src/utilities/index.ts` to prevent tree-shaking confusion (please import utilities directly from their respective modules)\n- Removed global `[hidden]` styles so they don't affect anything outside of components\n- Updated to Bootstrap Icons 1.5.0\n- Updated React docs to use [`@shoelace-style/react`](https://github.com/shoelace-style/react)\n- Updated NextJS docs [#434]\n- Updated TypeScript to 4.2.4\n\n## 2.0.0-beta.39\n\n- Added experimental `<sl-qr-code>` component\n- Added `system` icon library and updated all components to use this instead of the default icon library [#420]\n- Updated to esbuild 0.8.57\n- Updated to Lit 2.0.0-rc.1 and lit-html 2.0.0-rc.2\n\n## 2.0.0-beta.38\n\n- 🚨 BREAKING: `<sl-radio>` components must be located inside an `<sl-radio-group>` for proper accessibility [#218]\n- Added `<sl-radio-group>` component [#218]\n- Added `--header-spacing`, `--body-spacing`, and `--footer-spacing` custom properties to `<sl-drawer>` and `<sl-dialog>` [#409]\n- Fixed a bug where `<sl-menu-item>` prefix and suffix slots wouldn't always receive the correct spacing\n- Fixed a bug where `<sl-badge>` used `--sl-color-white` instead of the correct design tokens [#407]\n- Fixed a bug in `<sl-dialog>` and `<sl-drawer>` where the escape key would cause parent components to close\n- Fixed a race condition bug in `<sl-icon>` [#410]\n- Improved focus trap behavior in `<sl-dialog>` and `<sl-drawer>`\n- Improved a11y in `<sl-dialog>` and `<sl-drawer>` by restoring focus to trigger on close\n- Improved a11y in `<sl-radio>` with Windows high contrast mode [#215]\n- Improved a11y in `<sl-select>` by preventing the chevron icon from being announced\n- Internal: removed the `options` argument from the modal utility as focus trapping is now handled internally\n\n## 2.0.0-beta.37\n\n- Added `click()` method to `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>`\n- Added the `activation` attribute to `<sl-tab-group>` to allow for automatic and manual tab activation\n- Added `npm run create <tag>` script to scaffold new components faster\n- Fixed a bug in `<sl-tooltip>` where events weren't properly cleaned up on disconnect\n- Fixed a bug in `<sl-tooltip>` where they wouldn't display after toggling `disabled` off and on again [#391]\n- Fixed a bug in `<sl-details>` where `show()` and `hide()` would toggle the control when disabled\n- Fixed a bug in `<sl-color-picker>` where setting `value` wouldn't update the control\n- Fixed a bug in `<sl-tab-group>` where tabs that are initially disabled wouldn't receive the indicator on activation [#403]\n- Fixed incorrect event names for `sl-after-show` and `sl-after-hide` in `<sl-details>`\n- Improved a11y for disabled buttons that are rendered as links\n- Improved a11y for `<sl-button-group>` by adding the correct `role` attribute\n- Improved a11y for `<sl-input>`, `<sl-range>`, `<sl-select>`, and `<sl-textarea>` so labels and helper text are read properly by screen readers\n- Removed `sl-show`, `sl-hide`, `sl-after-show`, `sl-after-hide` events from `<sl-color-picker>` (the color picker's visibility cannot be controlled programmatically so these shouldn't have been exposed; the dropdown events now bubble up so you can listen for those instead)\n- Reworked `<sl-button-group>` so it doesn't require light DOM styles\n\n## 2.0.0-beta.36\n\n- 🚨 BREAKING: renamed `setFocus()` to `focus()` in button, checkbox, input, menu item, radio, range, rating, select, switch, and tab\n- 🚨 BREAKING: renamed `removeFocus()` to `blur()` in button, checkbox, input, menu item, radio, range, rating, select, switch, and tab\n- Added `click()` method to `<sl-button>`\n- Fixed a bug where toggling `open` on `<sl-drawer>` would skip the transition\n- Fixed a bug where `<sl-color-picker>` could be opened when disabled\n- Fixed a bug in `<sl-color-picker>` that caused erratic slider behaviors [#388]\n- Fixed a bug where `<sl-details>` wouldn't always render the correct height when open initially [#357]\n- Renamed `components.json` to `metadata.json`\n- Updated to the prerelease versions of LitElement and lit-html\n- Updated to Bootstrap Icons 1.4.1\n\n## 2.0.0-beta.35\n\n- Fixed a bug in `<sl-animation>` where `sl-cancel` and `sl-finish` events would never fire\n- Fixed a bug where `<sl-alert>` wouldn't always transition properly\n- Fixed a bug where using `<sl-menu>` inside a shadow root would break keyboard selections [#382]\n- Fixed a bug where toggling `multiple` in `<sl-select>` would lead to a stale display label\n- Fixed a bug in `<sl-tab-group>` where changing `placement` could result in the active tab indicator being drawn a few pixels off\n- Fixed a bug in `<sl-button>` where link buttons threw an error on focus, blur, and click\n- Improved `@watch` decorator to run after update instead of during\n- Updated `<sl-menu-item>` checked icon to `check` instead of `check2`\n- Upgraded the status of `<sl-resize-observer>` from experimental to stable\n\n## 2.0.0-beta.34\n\nThis release changes the way components are registered if you're [cherry picking](/getting-started/installation#cherry-picking) or [using a bundler](/getting-started/installation#bundling). This recommendation came from the LitElement team and simplifies Shoelace's dependency graph. It also eliminates the need to call a `register()` function before using each component.\n\nFrom now on, importing a component will register it automatically. The caveat is that bundlers may not tree shake the library properly if you import from `@shoelace-style/shoelace`, so the recommendation is to import components and utilities from their corresponding files instead.\n\n- 🚨 BREAKING: removed `all.shoelace.js` (use `shoelace.js` instead)\n- 🚨 BREAKING: component modules now have a side effect, so bundlers may not tree shake properly when importing from `@shoelace-style/shoelace` (see the [installation page](/getting-started/installation#bundling) for more details and how to update)\n- Added `sl-clear` event to `<sl-select>`\n- Fixed a bug where dynamically changing menu items in `<sl-select>` would cause the display label to be blank [#374]\n- Fixed a bug where setting the `value` attribute or property on `<sl-input>` and `<sl-textarea>` would trigger validation too soon\n- Fixed the margin in `<sl-menu-label>` to align with menu items\n- Fixed `autofocus` attributes in `<sl-input>` and `<sl-textarea>`\n- Improved types for `autocapitalize` in `<sl-input>` and `<sl-textarea>`\n- Reverted the custom `@tag` decorator in favor of `@customElement` to enable auto-registration\n\n## 2.0.0-beta.33\n\n- Fixed a bug where link buttons could have incorrect `target`, `download`, and `rel` props\n- Fixed `aria-label` and `aria-labelledby` props in `<sl-dialog>` and `<sl-drawer>`\n- Fixed `tabindex` attribute in `<sl-menu>`\n- Fixed a bug in `<sl-select>` where tags would always render as pills\n- Fixed a bug in `<sl-button>` where calling `setFocus()` would throw an error\n\n## 2.0.0-beta.32\n\n- Added tag name maps so TypeScript can identify Shoelace elements [#371]\n- Fixed a bug where the active tab indicator wouldn't render properly on tabs styled with `flex-end` [#355]\n- Fixed a bug where `sl-change` wasn't emitted by `<sl-checkbox>` or `<sl-switch>` [#370]\n- Fixed a bug where some props weren't being watched correctly in `<sl-alert>` and `<sl-color-picker>`\n- Improved `@watch` decorator so watch handlers don't run before the first render\n- Removed guards that were added due to previous watch handler behavior\n\n## 2.0.0-beta.31\n\n- Add touch support to `<sl-rating>` [#362]\n- Fixed a bug where the `open` attribute on `<sl-details>` would prevent it from opening [#357]\n- Fixed event detail type parsing so component class names are shown instead of `default`\n\n## 2.0.0-beta.30\n\n- Fix default exports for all components so cherry picking works again [#365]\n- Revert FOUC base style because it interferes with some framework and custom element use cases\n\n## 2.0.0-beta.29\n\n**This release migrates component implementations from Shoemaker to LitElement.** Due to feedback from the community, Shoelace will rely on a more heavily tested library for component implementations. This gives you a more solid foundation and reduces my maintenance burden. Thank you for all your comments, concerns, and encouragement! Aside from that, everything else from beta.28 still applies plus the following.\n\n- 🚨 BREAKING: removed the `symbol` property from `<sl-rating>` and reverted to `getSymbol` for optimal flexibility\n- Added `vscode.html-custom-data.json` to the build to support IntelliSense (see [the usage section](/getting-started/usage#code-completion) for details)\n- Added a base style to prevent FOUC before components are defined\n- Fixed bug where TypeScript types weren't being generated [#364]\n- Improved vertical padding in `<sl-tooltip>`\n- Moved chunk files into a separate folder\n- Reverted menu item active styles\n- Updated esbuild to 0.8.54\n\n## 2.0.0-beta.28\n\n**This release includes a major under the hood overhaul of the library and how it's distributed.** Until now, Shoelace was developed with Stencil. This release moves to a lightweight tool called Shoemaker, a homegrown utility that provides declarative templating and data binding while reducing the boilerplate required for said features.\n\nThis change in tooling addresses a number of longstanding bugs and limitations. It also gives us more control over the library and build process while streamlining development and maintenance. Instead of two different distributions, Shoelace now offers a single, standards-compliant collection of ES modules. This may affect how you install and use the library, so please refer to the [installation page](/getting-started/installation) for details.\n\n:::warning\nDue to the large number of internal changes, I would consider this update to be less stable than previous ones. If you're using Shoelace in a production app, consider holding off until the next beta to allow for more exhaustive testing from the community. Please report any bugs you find on the [issue tracker](https://github.com/shoelace-style/shoelace/issues).\n:::\n\nThe component API remains the same except for the changes noted below. Thanks for your patience as I work diligently to make Shoelace more stable and future-proof. 🙌\n\n- 🚨 BREAKING: removed the custom elements bundle (you can import ES modules directly)\n- 🚨 BREAKING: removed `getAnimationNames()` and `getEasingNames()` methods from `<sl-animation>` (you can import them from `utilities/animation.js` instead)\n- 🚨 BREAKING: removed the `<sl-icon-library>` component since it required imperative initialization (you can import the `registerIconLibrary()` function from `utilities/icon-library.js` instead)\n- 🚨 BREAKING: removed the experimental `<sl-theme>` component due to technical limitations (you should set the `sl-theme-{name}` class on the `<body>` instead)\n- 🚨 BREAKING: moved the base stylesheet from `dist/shoelace.css` to `dist/themes/base.css`\n- 🚨 BREAKING: moved `icons` into `assets/icons` to make future assets easier to colocate\n- 🚨 BREAKING: changed `getSymbol` property in `<sl-rating>` to `symbol` (it now accepts a string or a function that returns an icon name)\n- 🚨 BREAKING: renamed `setAssetPath()` to `setBasePath()` and added the ability to set the library's base path with a `data-shoelace` attribute (`setBasePath()` is exported from `utilities/base-path.js`)\n- Fixed `min` and `max` types in `<sl-input>` to allow numbers and strings [#330]\n- Fixed a bug where `<sl-checkbox>`, `<sl-radio>`, and `<sl-switch>` controls would shrink with long labels [#325]\n- Fixed a bug in `<sl-select>` where the dropdown menu wouldn't reposition when the box resized [#340]\n- Fixed a bug where ignoring clicks and clicking the overlay would prevent the escape key from closing the dialog/drawer [#344]\n- Removed the lazy loading dist (importing `shoelace.js` will load and register all components now)\n- Switched from Stencil to Shoemaker\n- Switched to a custom build powered by [esbuild](https://esbuild.github.io/)\n- Updated to Bootstrap Icons 1.4.0\n\n## 2.0.0-beta.27\n\n- Added `handle-icon` slot to `<sl-image-comparer>` [#311]\n- Added `label` and `helpText` props and slots to `<sl-range>` [#318]\n- Added \"Integrating with NextJS\" tutorial to the docs, courtesy of [crutchcorn](https://github.com/crutchcorn)\n- Added `content` slot to `<sl-tooltip>` [#322]\n- Fixed a bug in `<sl-select>` where removing a tag would toggle the dropdown\n- Fixed a bug in `<sl-input>` and `<sl-textarea>` where the input might not exist when the value watcher is called [#313]\n- Fixed a bug in `<sl-details>` where hidden elements would receive focus when tabbing [#323]\n- Fixed a bug in `<sl-icon>` where `sl-error` would only be emitted for network failures [#326]\n- Reduced the default line-height for `<sl-tooltip>`\n- Updated `<sl-menu-item>` focus styles\n- Updated `<sl-select>` so tags will wrap when `multiple` is true\n- Updated to Stencil 2.4.0\n\n## 2.0.0-beta.26\n\n- 🚨 BREAKING: Fixed animations bloat\n  - Removed ~400 baked-in Animista animations because they were causing ~200KB of bloat (they can still be used with custom keyframes)\n  - Reworked animations into a separate module ([`@shoelace-style/animations`](https://github.com/shoelace-style/animations)) so it's more maintainable and animations are sync with the latest version of animate.css\n  - Animation and easing names are now camelCase (e.g. `easeInOut` instead of `ease-in-out`)\n- Added initial E2E tests [#169]\n- Added the `FocusOptions` argument to all components that have a `setFocus()` method\n- Added `sl-initial-focus` event to `<sl-dialog>` and `<sl-drawer>` so focus can be customized to a specific element\n- Added `close-button` part to `<sl-tab>` so the close button can be customized\n- Added `scroll-button` part to `<sl-tab-group>` so the scroll buttons can be customized\n- Fixed a bug where `sl-hide` would be emitted twice when closing an alert with `hide()`\n- Fixed a bug in `<sl-color-picker>` where the toggle button was smaller than the preview button in Safari\n- Fixed a bug in `<sl-tab-group>` where activating a nested tab group didn't work properly [#299]\n- Fixed a bug in `<sl-tab-group>` where removing tabs would throw an error\n- Fixed a bug in `<sl-alert>`, `<sl-dialog>`, `<sl-drawer>`, `<sl-select>`, and `<sl-tag>` where the close button's base wasn't exported so it couldn't be styled\n- Removed `text` type from `<sl-badge>` as it was erroneously copied and never had styles\n- Updated `<sl-tab-group>` so the `active` property is reflected to its attribute\n- Updated the docs to show dependencies instead of dependents which is much more useful when working with the custom elements bundle\n- Updated to Bootstrap Icons 1.3.0\n\n## 2.0.0-beta.25\n\n- 🚨 BREAKING: Reworked color tokens\n  - Theme colors are now inspired by Tailwind's professionally-designed color palette\n  - Color token variations now range from 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950\n  - Color token variations were inverted, e.g. 50 is lightest and 950 is darkest\n  - All component styles were adapted to use the new color tokens, but visual changes are subtle\n  - The dark theme was adapted use the new color tokens\n  - HSL is no longer used because it is not perceptually uniform (this may be revisited when all browsers support [LCH colors](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/))\n- 🚨 BREAKING: Refactored `<sl-select>` to improve accessibility [#216]\n  - Removed the internal `<sl-input>` because it was causing problems with a11y and virtual keyboards\n  - Removed `input`, `prefix` and `suffix` parts\n- 🚨 BREAKING: Removed `copy-button` part from `<sl-color-picker>` since copying is now done by clicking the preview\n- Added `getFormattedValue()` method to `<sl-color-picker>` so you can retrieve the current value in any format\n- Added visual separators between solid buttons in `<sl-button-group>`\n- Added `help-text` attribute to `<sl-input>`, `<sl-textarea>`, and `<sl-select>`\n- Fixed a bug where moving the mouse while `<sl-dropdown>` is closing would remove focus from the trigger\n- Fixed a bug where `<sl-menu-item>` didn't set a default color in the dark theme\n- Fixed a bug where `<sl-color-picker>` preview wouldn't update in Safari\n- Fixed a bug where removing an icon's `name` or `src` wouldn't remove the previously rendered SVG [#285]\n- Fixed a bug where disabled link buttons didn't appear disabled\n- Improved button spacings and added split button example\n- Improved elevation tokens in dark theme\n- Improved accessibility in `<sl-tooltip>` by allowing escape to dismiss it [#219]\n- Improved slot detection in `<sl-card>`, `<sl-dialog>`, and `<sl-drawer>`\n- Made `@types/resize-observer-browser` a dependency so users don't have to install it manually\n- Refactored internal label + help text logic into a functional component used by `<sl-input>`, `<sl-textarea>`, and `<sl-select>`\n- Removed `sl-blur` and `sl-focus` events from `<sl-menu>` since menus can't have focus as of 2.0.0-beta.22\n- Updated `<sl-spinner>` so the indicator is more obvious\n- Updated to Bootstrap Icons 1.2.2\n\n## 2.0.0-beta.24\n\n- Added `<sl-format-date>` component\n- Added `indeterminate` state to `<sl-progress-bar>` [#274]\n- Added `--track-color`, `--indicator-color`, and `--label-color` to `<sl-progress-bar>` [#276]\n- Added `allow-scripts` attribute to `<sl-include>` [#280]\n- Fixed a bug where `<sl-menu-item>` color variable was incorrect [#272]\n- Fixed a bug where `<sl-dialog>` and `<sl-drawer>` would emit the `sl-hide` event twice [#275]\n- Fixed a bug where calling `event.preventDefault()` on certain form elements wouldn't prevent `<sl-form>` from submitting [#277]\n- Fixed drag handle orientation in `<sl-image-comparer>`\n- Restyled `<sl-spinner>` so the track is visible and the indicator is smaller.\n- Removed `resize-observer-polyfill` in favor of `@types/resize-observer-browser` since all target browsers support `ResizeObserver`\n- Upgraded the status of `<sl-form>`, `<sl-image-comparer>`, and `<sl-include>` from experimental to stable\n\n## 2.0.0-beta.23\n\n- Added `<sl-format-number>` component\n- Added `<sl-relative-time>` component\n- Added `closable` attribute to `<sl-tab>`\n- Added experimental `<sl-resize-observer>` utility\n- Added experimental `<sl-theme>` utility and updated theming documentation\n- Fixed a bug where `<sl-menu-item>` wouldn't render properly in the dark theme\n- Fixed a bug where `<sl-select>` would show an autocomplete menu\n- Improved placeholder contrast in dark theme\n- Updated to Bootstrap Icons 1.1.0\n- Updated to Stencil 2.3.0\n\n## 2.0.0-beta.22\n\n- 🚨 BREAKING: Refactored `<sl-menu>` and `<sl-menu-item>` to improve accessibility by using proper focus states [#217]\n  - Moved `tabindex` from `<sl-menu>` to `<sl-menu-item>`\n  - Removed the `active` attribute from `<sl-menu-item>` because synthetic focus states are bad for accessibility\n  - Removed the `sl-activate` and `sl-deactivate` events from `<sl-menu-item>` (listen for `focus` and `blur` instead)\n  - Updated `<sl-select>` so keyboard navigation still works\n- Added `no-scroll-controls` attribute to `<sl-tab-group>` [#253]\n- Fixed a bug where setting `open` initially wouldn't show `<sl-dialog>` or `<sl-drawer>` [#255]\n- Fixed a bug where `disabled` could be set when buttons are rendered as links\n- Fixed a bug where hoisted dropdowns would render in the wrong position when placed inside `<sl-dialog>` [#252]\n- Fixed a bug where boolean aria attributes didn't explicitly set `true|false` string values in the DOM\n- Fixed a bug where `aria-describedby` was never set on tooltip targets in `<sl-tooltip>`\n- Fixed a bug where setting `position` on `<sl-image-comparer>` wouldn't update the divider's position\n- Fixed a bug where the check icon was announced to screen readers in `<sl-menu-item>`\n- Improved `<sl-icon-button>` accessibility by encouraging proper use of `label` and hiding the internal icon from screen readers [#220]\n- Improved `<sl-dropdown>` accessibility by attaching `aria-haspopup` and `aria-expanded` to the slotted trigger\n- Refactored position logic to remove an unnecessary state variable in `<sl-image-comparer>`\n- Refactored design tokens to use `rem` instead of `px` for input height and spacing [#221]\n- Removed `console.log` from modal utility\n- Updated to Stencil 2.2.0\n\n## 2.0.0-beta.21\n\n- Added `label` slot to `<sl-input>`, `<sl-select>`, and `<sl-textarea>` [#248]\n- Added `label` slot to `<sl-dialog>` and `<sl-drawer>`\n- Added experimental `<sl-include>` component\n- Added status code to the `sl-error` event in `<sl-icon>`\n- Fixed a bug where initial transitions didn't show in `<sl-dialog>` and `<sl-drawer>` [#247]\n- Fixed a bug where indeterminate checkboxes would maintain the indeterminate state when toggled\n- Fixed a bug where concurrent active modals (i.e. dialog, drawer) would try to steal focus from each other\n- Improved `<sl-color-picker>` grid and slider handles [#246]\n- Refactored `<sl-icon>` request logic and removed unused cache map\n- Reworked show/hide logic in `<sl-alert>`, `<sl-dialog>`, and `<sl-drawer>` to not use reflow hacks and the `hidden` attribute\n- Reworked slot logic in `<sl-card>`, `<sl-dialog>`, and `<sl-drawer>`\n- Updated to Popper 2.5.3 to address a fixed position bug in Firefox\n\n## 2.0.0-beta.20\n\n- 🚨 BREAKING: Transformed all Shoelace events to lowercase ([details](#why-did-event-names-change))\n- Added support for dropdowns and non-icon elements to `<sl-input>`\n- Added `spellcheck` attribute to `<sl-input>`\n- Added `<sl-icon-library>` to allow custom icon library registration\n- Added `library` attribute to `<sl-icon>` and `<sl-icon-button>`\n- Added \"Integrating with Rails\" tutorial to the docs, courtesy of [ParamagicDev](https://github.com/ParamagicDev)\n- Fixed a bug where `<sl-progress-ring>` rendered incorrectly when zoomed in Safari [#227]\n- Fixed a bug where tabbing into slotted elements closes `<sl-dropdown>` when used in a shadow root [#223]\n- Fixed a bug where scroll anchoring caused undesirable scrolling when `<sl-details>` are grouped\n\nShoelace events were updated to use a lowercase, kebab-style naming convention. Instead of event names such as `slChange` and `slAfterShow`, you'll need to use `sl-change` and `sl-after-show` now.\n\nThis change was necessary to address a critical issue in frameworks that use DOM templates with declarative event bindings such as `<sl-button @slChange=\"handler\">`. Due to HTML's case-insensitivity, browsers translate attribute names to lowercase, turning `@slChange` into `@slchange`, making it impossible to listen to `slChange`.\n\nWhile declarative event binding is a non-standard feature, not supporting it would make Shoelace much harder to use in popular frameworks. To accommodate those users and provide a better developer experience, we decided to change the naming convention while Shoelace is still in beta.\n\nThe following pages demonstrate why this change was necessary.\n\n- [This Polymer FAQ from Custom Elements Everywhere](https://custom-elements-everywhere.com/#faq-polymer)\n- [Vue's Event Names documentation](https://vuejs.org/v2/guide/components-custom-events.html#Event-Names)\n\n## 2.0.0-beta.19\n\n- Added `input`, `label`, `prefix`, `clear-button`, `suffix`, `help-text` exported parts to `<sl-select>` to make the input customizable\n- Added toast notifications through the `toast()` method on `<sl-alert>`\n- Fixed a bug where mouse events would bubble up when `<sl-button>` was disabled, causing tooltips to erroneously appear\n- Fixed a bug where pressing space would open and immediately close `<sl-dropdown>` panels in Firefox\n- Fixed a bug where `<sl-tooltip>` would throw an error on init\n- Fixed a bug in custom keyframes animation example\n- Refactored clear logic in `<sl-input>`\n\n## 2.0.0-beta.18\n\n- Added `name` and `invalid` attribute to `<sl-color-picker>`\n- Added support for form submission and validation to `<sl-color-picker>`\n- Added touch support to demo resizers in the docs\n- Added `<sl-responsive-embed>` component\n- Fixed a bug where swapping an animated element wouldn't restart the animation in `<sl-animation>`\n- Fixed a bug where the cursor was incorrect when `<sl-select>` was disabled\n- Fixed a bug where `slblur` and `slfocus` were emitted twice in `<sl-select>`\n- Fixed a bug where clicking on `<sl-menu>` wouldn't focus it\n- Fixed a bug in the popover utility where `onAfterShow` would fire too soon\n- Fixed a bug where `bottom` and `right` placements didn't render properly in `<sl-tab-group>`\n- Improved keyboard logic in `<sl-dropdown>`, `<sl-menu>`, and `<sl-select>`\n- Updated `<sl-animation>` to stable\n- Updated to Stencil 2.0 (you may need to purge `node_modules` and run `npm install` after pulling)\n- Updated entry points in `package.json` to reflect new filenames generated by Stencil 2\n\n## 2.0.0-beta.17\n\n- Added `minlength` and `spellcheck` attributes to `<sl-textarea>`\n- Fixed a bug where clicking a tag in `<sl-select>` wouldn't toggle the menu\n- Fixed a bug where options where `<sl-select>` options weren't always visible or scrollable\n- Fixed a bug where setting `null` on `<sl-input>`, `<sl-textarea>`, or `<sl-select>` would throw an error\n- Fixed a bug where `role` was on the wrong element and aria attribute weren't explicit in `<sl-checkbox>`, `<sl-switch>`, and `<sl-radio>`\n- Fixed a bug where dynamically adding/removing a slot wouldn't work as expected in `<sl-card>`, `<sl-dialog>`, and `<sl-drawer>`\n- Fixed a bug where the value wasn't updated and events weren't emitted when using `setRangeText` in `<sl-input>` and `<sl-textarea>`\n- Optimized `hasSlot` utility by using a simpler selector\n- Updated Bootstrap Icons to 1.0.0 with many icons redrawn and improved\n- Updated contribution guidelines\n\n**Form validation has been reworked and is much more powerful now!**\n\n- The `invalid` attribute now reflects the control's validity as determined by the browser's constraint validation API\n- Added `required` to `<sl-checkbox>`, `<sl-select>`, and `<sl-switch>`\n- Added `reportValidity()` and `setCustomValidity()` methods to all form controls\n- Added validation checking for custom and native form controls to `<sl-form>`\n- Added `novalidate` attribute to `<sl-form>` to disable validation\n- Removed the `valid` attribute from all form controls\n- Removed valid and invalid design tokens and related styles (you can use your own custom styles to achieve this)\n\n## 2.0.0-beta.16\n\n- Added `hoist` attribute to `<sl-color-picker>`, `<sl-dropdown>`, and `<sl-select>` to work around panel clipping\n- Added `<sl-format-bytes>` utility component\n- Added `clearable` and `required` props to `<sl-select>`\n- Added `slclear` event to `<sl-input>`\n- Added keyboard support to the preview resizer in the docs\n- Fixed a bug where the `aria-selected` state was incorrect in `<sl-menu-item>`\n- Fixed a bug where custom properties applied to `<sl-tooltip>` didn't affect show/hide transitions\n- Fixed a bug where `--sl-input-color-*` custom properties had no effect on `<sl-input>` and `<sl-textarea>`\n- Refactored `<sl-dropdown>` and `<sl-tooltip>` to use positioner elements so panels/tooltips can be customized easier\n\n## 2.0.0-beta.15\n\n- Added `image-comparer` component\n- Added `--width`, `--height`, and `--thumb-size` custom props to `<sl-switch>`\n- Fixed an `aria-labelledby` attribute typo in a number of components\n- Fixed a bug where the `change` event wasn't updating the value in `<sl-input>`\n- Fixed a bug where `<sl-color-picker>` had the wrong border color in the dark theme\n- Fixed a bug where `<sl-menu-item>` had the wrong color in dark mode when disabled\n- Fixed a bug where WebKit's autocomplete styles made inputs looks broken\n- Fixed a bug where aria labels were wrong in `<sl-select>`\n- Fixed a bug where clicking the label wouldn't focus the control in `<sl-select>`\n\n## 2.0.0-beta.14\n\n- Added dark theme\n- Added `--sl-panel-background-color` and `--sl-panel-border-color` tokens\n- Added `--tabs-border-color` custom property to `<sl-tab-group>`\n- Added `--track-color` custom property to `<sl-range>`\n- Added `tag` part to `<sl-select>`\n- Updated `package.json` so custom elements imports can be consumed from the root\n- Fixed a bug where scrolling dialogs didn't resize properly in Safari\n- Fixed a bug where `slshow` and `slhide` would be emitted twice in some components\n- Fixed a bug where `custom-elements/index.d.ts` was broken due to an unclosed comment (fixed in Stencil 1.17.3)\n- Fixed bug where inputs were not using border radius tokens\n- Fixed a bug where the text color was being erroneously set in `<sl-progress-ring>`\n- Fixed a bug where `<sl-progress-bar>` used the wrong part name internally for `indicator`\n- Removed background color from `<sl-menu>`\n- Updated to Stencil 1.17.3\n\n## 2.0.0-beta.13\n\n- Added `slactivate` and `sldeactivate` events to `<sl-menu-item>`\n- Added experimental `<sl-animation>` component\n- Added shields to documentation\n- Fixed a bug where link buttons would have `type=\"button\"`\n- Fixed a bug where button groups with tooltips experienced an odd spacing issue in Safari\n- Fixed a bug where scrolling in dropdowns/selects didn't work properly on Windows (special thanks to [Trendy](http://github.com/trendy) for helping troubleshoot!)\n- Fixed a bug where selecting a menu item in a dropdown would cause Safari to scroll\n- Fixed a bug where type to select wouldn't accept symbols\n- Moved scrolling logic from `<sl-menu>` to `<sl-dropdown>`\n\n## 2.0.0-beta.12\n\n- Added support for `href`, `target`, and `download` to buttons\n- Fixed a bug where buttons would have horizontal spacing in Safari\n- Fixed a bug that caused an import resolution error when using Shoelace in a Stencil app\n\n## 2.0.0-beta.11\n\n- Added button group component\n- Fixed icon button alignment\n- Fixed a bug where focus visible observer wasn't removed from `<sl-details>`\n- Replaced the deprecated `componentDidUnload` lifecycle method with `disconnectedCallback` to prevent issues with frameworks\n\n## 2.0.0-beta.10\n\n- Added community page to the docs\n- Fixed a bug where many components would erroneously receive an `id` when using the custom elements bundle\n- Fixed a bug where tab groups weren't scrollable with the mouse\n\n## 2.0.0-beta.9\n\n- Added the icon button component\n- Added the skeleton component\n- Added the `typeToSelect` method to menu so type-to-select behavior can be controlled externally\n- Added the `pulse` attribute to badge\n- Fixed a bug where hovering over select showed the wrong cursor\n- Fixed a bug where tabbing into a select control would highlight the label\n- Fixed a bug where tabbing out of a select control wouldn't close it\n- Fixed a bug where closing dropdowns wouldn't give focus back to the trigger\n- Fixed a bug where type-to-select wasn't working after the first letter\n- Fixed a bug where clicking on menu items and dividers would steal focus from the menu\n- Fixed a bug where the color picker wouldn't parse uppercase values\n- Removed the `no-footer` attribute from dialog and drawer (slot detection is automatic, so the attribute is not required)\n- Removed `close-icon` slot from alert\n- Replaced make-shift icon buttons with `<sl-icon-button>` in alert, dialog, drawer, and tag\n- Updated Stencil to 1.17.1\n- Switched to jsDelivr for better CDN performance\n\n## 2.0.0-beta.8\n\n- Added the card component\n- Added `--focus-ring` custom property to tab\n- Fixed a bug where range tooltips didn't appear on iOS\n- Fixed constructor bindings so they don't break the custom elements bundle\n- Fixed tag color contrast to be AA compliant\n- Fixed a bug that made it difficult to vertically align rating\n- Fixed a bug where dropdowns would always close on mousedown when inside a shadow root\n- Made tag text colors AA compliant\n- Promoted badge to stable\n- Refactored `:host` variables and moved non-display props to base elements\n- Refactored event handler bindings to occur in `connectedCallback` instead of the constructor\n- Refactored scroll locking logic to use `Set` instead of an array\n- Updated the custom elements bundle documentation and added bundler examples\n- Upgraded Stencil to 1.17.0-0 (next) to fix custom elements bundle\n\n## 2.0.0-beta.7\n\n- Added links to version 1 resources to the docs\n- Added rating component\n- Fixed a bug where some build files were missing\n- Fixed clearable tags demo\n- Fixed touch icon size in docs\n\n## 2.0.0-beta.6\n\n- Enabled the `dist-custom-elements-bundle` output target\n- Fixed a bug where nested form controls were ignored in `<sl-form>`\n\n## 2.0.0-beta.5\n\n- Fixed bug where `npm install` would fail due to postinstall script\n- Removed unused dependency\n\n## 2.0.0-beta.4\n\n- Added `pill` variation to badges\n- Fixed a bug where all badges had `pointer-events: none`\n- Fixed `@since` props to show 2.0 instead of 1.0\n- Fixed giant cursors in inputs in Safari\n- Fixed color picker input width in Safari\n- Fixed initial transitions for drawer, dialog, and popover consumers\n- Fixed a bug where dialog, dropdown, and drawer would sometimes not transition in on the first open\n- Fixed various documentation typos\n\n## 2.0.0-beta.3\n\n- Fix version in docs\n- Remove custom elements bundle\n\n## 2.0.0-beta.2\n\n- Fix quick start and installation URLs\n- Switch Docsify theme\n- Update line heights tokens\n\n## 2.0.0-beta.1\n\n- Initial release\n"
  },
  {
    "path": "docs/pages/resources/community.md",
    "content": "---\nmeta:\n  title: Community\n  description: Shoelace has a growing community of designers and developers that are building amazing things with web components.\n---\n\n# Community\n\nShoelace has a growing community of designers and developers that are building amazing things with web components. We'd love for you to become a part of it!\n\nPlease be respectful of other users and remember that Shoelace is an open source project. We'll try to help when we can, but there's no guarantee we'll be able solve your problem. Please manage your expectations and don't forget to contribute back to the conversation when you can!\n\n## Discussion Forum\n\nThe [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is open to anyone with a GitHub account. This is the best place to:\n\n- Ask for help\n- Share ideas and get feedback\n- Show the community what you're working on\n- Learn more about the project, its values, and its roadmap\n\n<sl-button variant=\"primary\" href=\"https://github.com/shoelace-style/shoelace/discussions\" target=\"_blank\">\n  <sl-icon name=\"github\" slot=\"prefix\"></sl-icon>\n  Join the Discussion\n</sl-button>\n\n## Community Chat\n\nThe [community chat](https://discord.gg/mg8f26C) is open to the public and powered by [Discord](https://discord.com/). This is a good place to:\n\n- Ask for help\n- Share ideas and get feedback\n- Show the community what you're working on\n- Chat live with other designers, developers, and Shoelace fans\n\n<sl-button variant=\"primary\" href=\"https://discord.gg/mg8f26C\" target=\"_blank\">\n  <sl-icon name=\"discord\" slot=\"prefix\"></sl-icon>\n  Join the Chat\n</sl-button>\n\n## Stack Overflow\n\nYou can post questions on Stack Overflow using [the \"shoelace\" tag](https://stackoverflow.com/questions/tagged/shoelace). This is a public forum where talented developers answer questions. It's a great way to get help, but it is not maintained by the Shoelace author.\n\n<sl-button variant=\"primary\" href=\"https://stackoverflow.com/questions/ask?tags=shoelace\" target=\"_blank\">\n  <sl-icon name=\"stack-overflow\" slot=\"prefix\"></sl-icon>\n  Ask for Help\n</sl-button>\n\n## Twitter\n\nFollow [@shoelace_style](https://twitter.com/shoelace_style) on Twitter for general updates and announcements about Shoelace. This is a great place to say \"hi\" or to share something you're working on. You're also welcome to follow [@cory_laviska](https://twitter.com/cory_laviska), the creator, for tweets about web components, web development, and life.\n\n**Please avoid using Twitter for support questions.** The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is a much better place to share code snippets, screenshots, and other troubleshooting info. You'll have much better luck there, as more users will have a chance to help you.\n\n<sl-button variant=\"primary\" href=\"https://twitter.com/shoelace_style\" target=\"_blank\">\n  <sl-icon name=\"twitter\" slot=\"prefix\"></sl-icon>\n  Follow on Twitter\n</sl-button>\n"
  },
  {
    "path": "docs/pages/resources/contributing.md",
    "content": "---\nmeta:\n  title: Contributing\n  description: Shoelace is an open source project, meaning everyone can use it and contribute to its development.\n---\n\n# Contributing\n\nShoelace is an open source project, meaning everyone can use it and contribute to its development. When you join our community, you'll find a friendly group of enthusiasts at all experience levels who are willing to chat about anything and everything related to Shoelace.\n\nThe easiest way to get started contributing is to join the [community chat](https://discord.gg/mg8f26C). This is where we hang out, discuss new ideas, ask for feedback, and more!\n\nA common misconception about contributing to an open source project is that you need to know how to code. This simply isn't true. In fact, there are _many_ ways to contribute, and some of the most important contributions come from those who never write a single line of code. Here's a list of ways you can make a meaningful contribution to the project:\n\n- Submitting well-written bug reports\n- Submitting feature requests that are within the scope of the project\n- Improving the documentation\n- Responding to users that need help in the community chat or discussion forum\n- Triaging issues on GitHub\n- Being a developer advocate for the project\n- Sponsoring the project financially\n- Writing tests\n- Sharing ideas\n- And, of course, contributing code!\n\nPlease take a moment to review these guidelines to make the contribution process as easy as possible for both yourself and the project's maintainers.\n\n## AI-generated Code\n\nAs an open source maintainer, I respectfully ask that you refrain from using AI-generated code when contributing to this project. This includes code generated by tools such as GitHub Copilot, even if you make alterations to it afterwards. While some of Copilot's features are indeed convenient, the ethics surrounding which codebases the AI has been trained on and their corresponding software licenses remain very questionable and have yet to be tested in a legal context.\n\nI realize that one cannot reasonably enforce this any more than one can enforce not copying licensed code from other codebases, nor do I wish to expend energy policing contributors. I would, however, like to avoid all ethical and legal challenges that result from using AI-generated code. As such, I respectfully ask that you refrain from using such tools when contributing to this project. At this time, I will not knowingly accept any code that has been generated in such a manner.\n\n## Using the Issue Tracker\n\nThe [issue tracker](https://github.com/shoelace-style/shoelace/issues) is for bug reports, feature requests, and pull requests.\n\n- Please **do not** use the issue tracker for personal support requests. Use [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/help) instead.\n- Please **do not** use the issue tracker for feature requests. Use [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/ideas) instead.\n- Please **do not** derail, hijack, or troll issues. Keep the discussion on topic and be respectful of others.\n- Please **do not** post comments with \"+1\" or \"👍\". Use [reactions](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) instead.\n- Please **do** use the issue tracker for feature requests, bug reports, and pull requests.\n\nIssues that do not follow these guidelines are subject to closure. There simply aren't enough resources for the author and contributors to troubleshoot personal support requests.\n\n### Feature Requests\n\nFeature requests can be added using [the discussion forum](https://github.com/shoelace-style/shoelace/discussions/categories/ideas).\n\n- Please **do** search for an existing request before suggesting a new feature.\n- Please **do** use the voting buttons to vote for a feature.\n- Please **do** share substantial use cases and perspective that support new features if they haven't already been mentioned.\n- Please **do not** bump, spam, or ping contributors to prioritize your own feature.\n\n### Bug Reports\n\nA bug is _a demonstrable problem_ caused by code in the library. Bug reports are an important contribution to the quality of the project. When submitting a bug report, there are a few steps you can take to make sure your issues gets attention quickly.\n\n- Please **do not** paste in large blocks of irrelevant code\n- Please **do** search for an existing issue before creating a new one\n- Please **do** explain the bug clearly\n- Please **do** provide a minimal test case that demonstrates the bug (e.g. [jsfiddle.net](https://jsfiddle.net/) or [CodePen](https://codepen.io/))\n- Please **do** provide additional information, when necessary, to replicate the bug\n\n**A minimal test case is critical to a successful bug report.** It demonstrates that the bug exists in the library and not in surrounding code. Contributors should be able to understand the bug without studying your code, otherwise they'll probably move on to another bug.\n\n### Pull Requests\n\nTo keep the project on track, please consider the following guidelines before submitting a PR.\n\n- Please **do not** submit a PR without opening an issue first, unless the changes are trivial (e.g. fixing typos or outdated docs). This may prevent you from doing work that won't be accepted for various reasons (e.g. someone is already working on it, it's not a good fit for the project's roadmap, it needs additional planning, etc.)\n- Please **do** make sure your PR clearly defines what you're changing. Even if you feel your changes are obvious, please explain them so other contributors can more easily review your works. PRs without detailed descriptions are subject to closure pending more details.\n- Please **do** open your PR against the `next` branch.\n- Please **do not** edit anything in `dist/`. These files are generated automatically, so you need to edit the source files instead.\n\nThe author reserves the right to reject any PR that's outside the scope of the project or doesn't meet code quality standards.\n\n### Branches\n\n`current` - This branch reflects the latest release and powers [shoelace.style](https://shoelace.style/).\n\n`next` - This is the branch you should submit pull requests against. It reflects what's coming in the _next_ release, which can be previewed at [next.shoelace.style](https://next.shoelace.style/).\n\n## Developing\n\nTo set up a local dev environment, [fork the repo](https://github.com/shoelace-style/shoelace/fork) on GitHub, clone it locally, and install its dependencies.\n\n```bash\ngit clone https://github.com/YOUR_GITHUB_USERNAME/shoelace\ncd shoelace\nnpm install\n```\n\nOnce you've cloned the repo, run the following command to spin up the dev server.\n\n```bash\nnpm start\n```\n\nAfter the initial build, a browser will open automatically to a local version of the docs. The documentation is powered by Eleventy and a number of custom plugins.\n\n### Cloud-based Development\n\nAlternatively, you can use [Gitpod](https://www.gitpod.io/) to setup a dev environment in the cloud using only your browser.\n\n[![Open in Gitpod](/assets/images/gitpod.svg)](https://gitpod.io/#https://github.com/shoelace-style/shoelace)\n\n### Creating New Components\n\nTo scaffold a new component, run the following command, replacing `sl-tag-name` with the desired tag name.\n\n```bash\nnpm run create sl-tag-name\n```\n\nThis will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the \"Components\" section of the sidebar.\n\n### Dev Sandbox\n\nComponent development occurs _within_ the local docs site. I've found that offering common variations _in the docs_ is more beneficial for users than segmenting demos and code examples into separate tools such as Storybook. This encourages more thorough documentation, streamlines development for maintainers, and simplifies how the project is built. It also reduces installation and startup times significantly.\n\nThere is currently no hot module reloading (HMR), as browsers don't provide a way to unregister custom elements. However, most changes to the source will reload the browser automatically.\n\nFor more information about running and building the project locally, refer to `README.md` in the project's root.\n\n### Testing\n\nShoelace uses [Web Test Runner](https://modern-web.dev/guides/test-runner/getting-started/) for testing. To launch the test runner during development, open a terminal and launch the dev server.\n\n```bash\nnpm start\n```\n\nIn a second terminal window, launch the test runner.\n\n```bash\nnpm run test:watch\n```\n\nFollow the on-screen instructions to work with the test runner. Tests will automatically re-run as you make changes.\n\nTo run all tests only once:\n\n```bash\nnpm run test\n```\n\nTo test a single component, use the component's basename as shown in the following example.\n\n```bash\nnpm run test:component breadcrumb-item\n```\n\n## Documentation\n\nMaintaining good documentation can be a painstaking task, but poor documentation leads to frustration and makes the project less appealing to users. Fortunately, writing documentation for Shoelace is fast and easy!\n\nMost of Shoelace's technical documentation is generated with JSDoc comments and TypeScript metadata from the source code. Every property, method, event, etc. is documented this way. In-code comments encourage contributors to keep the documentation up to date as changes occur so the docs are less likely to become stale. Refer to an existing component to see how JSDoc comments are used in Shoelace.\n\nInstructions, code examples, and interactive demos are hand-curated to give users the best possible experience. Typically, the most relevant information is shown first and less common examples are shown towards the bottom. Edge cases and gotchas should be called out in context with tips or warnings.\n\nThe docs are powered by [Eleventy](https://www.11ty.dev/). Check out `docs/components/*.md` to get an idea of how pages are structured and formatted. If you're creating a new component, it may help to use an existing component's markdown file as a template.\n\nIf you need help with documentation, feel free to reach out on the [community chat](https://discord.gg/mg8f26C).\n\n### Shoelace-flavoured Markdown\n\nThe Shoelace documentation uses an extended version of [markdown-it](https://github.com/markdown-it/markdown-it). Generally speaking, it follows the [Commonmark spec](https://spec.commonmark.org/) while sprinkling in some additional features.\n\n#### Code Previews\n\nTo render a code preview, use the standard code field syntax and append `:preview` to the language.\n\n````md\n```html:preview\n[code goes here]\n```\n````\n\nYou can also append `:expanded` to expand the code by default, and `:no-codepen` to disable the CodePen button. The order of these modifiers doesn't matter, but no spaces should exist between the language and the modifiers.\n\n````md\n```html:preview:expanded:no-codepen\n[code goes here]\n```\n````\n\nThis particular syntax was chosen for a few reasons:\n\n1. It's easy to remember\n2. It works out of the box with markdown-it\n3. It appears to have the best support across editors and previewers (the language is usually highlighted correctly)\n\n#### Callouts\n\nSpecial callouts can be added using the following syntax.\n\n```\n:::tip\nThis is a tip/informational callout\n:::\n\n:::warning\nThis is a warning callout\n:::\n\n:::danger\nThis is a danger callout\n:::\n```\n\n#### Asides\n\nTo place content that's indirectly related, use the following syntax.\n\n```\n:::aside\nThis content is indirectly related and will appear in an `<aside>` element.\n:::\n```\n\n#### Details\n\nTo provide additional details that can be expanded/collapses, use the following syntax.\n\n```\n:::details Title Here\nThe details here are expandable.\n:::\n```\n\n#### GitHub Issues\n\nTo link to a GitHub issue, PR, or discussion, use the following syntax.\n\n```\n[#1234]\n```\n\n## Best Practices\n\nThe following is a non-exhaustive list of conventions, patterns, and best practices we try to follow. As a contributor, we ask that you make a good faith effort to follow them as well. This ensures consistency and maintainability throughout the project.\n\nIf in doubt, use your best judgment and the maintainers will be happy to guide you during the code review process. If you'd like clarification on something before submitting a PR, feel free to reach out on the [community chat](https://discord.gg/mg8f26C).\n\n:::tip\nThis section can be a lot to digest in one sitting, so don't feel like you need to take it all in right now. Most contributors will be better off skimming this section and reviewing the relevant content as needed.\n:::\n\n### Accessibility\n\nShoelace is built with accessibility in mind. Creating generic components that are fully accessible to users with varying capabilities across a multitude of circumstances is a daunting challenge. Oftentimes, the solution to an a11y problem is not written in black and white and, therefore, we may not get it right the first time around. There are, however, guidelines we can follow in our effort to make Shoelace an accessible foundation from which applications and websites can be built.\n\nWe take this commitment seriously, so please ensure your contributions have this goal in mind. If you need help with anything a11y-related, please [reach out to the community](/resources/community) for assistance. If you discover an accessibility concern within the library, please file a bug on the [issue tracker](https://github.com/shoelace-style/shoelace/issues).\n\nIt's important to remember that, although accessibility starts with foundational components, it doesn't end with them. It everyone's responsibility to encourage best practices and ensure we're providing an optimal experience for all of our users.\n\n### Code Formatting\n\nMost code formatting is handled automatically by [Prettier](https://prettier.io/) via commit hooks. However, for the best experience, you should [install it in your editor](https://prettier.io/docs/en/editors.html) and enable format on save.\n\nPlease do not make any changes to `prettier.config.cjs` without consulting the maintainers.\n\n### Composability\n\nComponents should be composable, meaning you can easily reuse them with and within other components. This reduces the overall size of the library, expedites feature development, and maintains a consistent user experience.\n\n### Component Structure\n\nAll components have a host element, which is a reference to the `<sl-*>` element itself. Make sure to always set the host element's `display` property to the appropriate value depending on your needs, as the default is `inline` per the custom element spec.\n\n```css\n:host {\n  display: block;\n}\n```\n\nAside from `display`, avoid setting styles on the host element when possible. The reason for this is that styles applied to the host element are not encapsulated. Instead, create a base element that wraps the component's internals and style that instead. This convention also makes it easier to use BEM in components, as the base element can serve as the \"block\" entity.\n\nWhen authoring components, please try to follow the same structure and conventions found in other components. Classes, for example, generally follow this structure:\n\n- Static properties/methods\n- Private/public properties (that are _not_ reactive)\n- `@query` decorators\n- `@state` decorators\n- `@property` decorators\n- Lifecycle methods (`connectedCallback()`, `disconnectedCallback()`, `firstUpdated()`, etc.)\n- Private methods\n- `@watch` decorators\n- Public methods\n- The `render()` method\n\nPlease avoid using the `public` keyword for class fields. It's simply too verbose when combined with decorators, property names, and arguments. However, _please do_ add `private` in front of any property or method that is intended to be private.\n\n:::tip\nThis might seem like a lot, but it's fairly intuitive once you start working with the library. However, don't let this structure prevent you from submitting a PR. [Code can change](https://www.abeautifulsite.net/posts/code-can-change/) and nobody will chastise you for \"getting it wrong.\" At the same time, encouraging consistency helps keep the library maintainable and easy for others to understand. (A lint rule that helps with things like this would be a very welcome PR!)\n:::\n\n### Class Names\n\nAll components use a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), so styles are completely encapsulated from the rest of the document. As a result, class names used _inside_ a component won't conflict with class names _outside_ the component, so we're free to name them anything we want.\n\nInternally, each component uses the [BEM methodology](http://getbem.com/) for class names. There is no technical requirement to do this — it's purely the preference of the author to enforce consistency and clarity throughout components. As such, all contributions are expected to follow this pattern.\n\n### Boolean Props\n\nBoolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noHeader` and a corresponding kebab-case attribute such as `no-header`.\n\nWhen naming boolean props that hide or disable things, prefix them with `no-`, e.g. `no-spin-buttons` and avoid using other verbs such as `hide-` and `disable-` for consistency.\n\n### Conditional Slots\n\nWhen a component relies on the presence of slotted content to do something, don't assume its initial state is permanent. Slotted content can be added or removed any time and components must be aware of this. A good practice to manage this is:\n\n- Add `@slotchange={this.handleSlotChange}` to the slots you want to watch\n- Add a `handleSlotChange` method and use the `hasSlot` utility to update state variables for the the respective slot(s)\n- Never conditionally render `<slot>` elements in a component — always use `hidden` so the slot remains in the DOM and the `slotchange` event can be captured\n\nSee the source of card, dialog, or drawer for examples.\n\n### Dynamic Slot Names and Expand/Collapse Icons\n\nA pattern has been established in `<sl-details>` and `<sl-tree-item>` for expand/collapse icons that animate on open/close. In short, create two slots called `expand-icon` and `collapse-icon` and render them both in the DOM, using CSS to show/hide only one based on the current open state. Avoid conditionally rendering them. Also avoid using dynamic slot names, such as `<slot name=${open ? 'open' : 'closed'}>`, because Firefox will not animate them.\n\nThere should be a container element immediately surrounding both slots. The container should be animated with CSS by default and it should have a part so the user can override the animation or disable it. Please refer to the source and documentation for `<sl-details>` and/or `<sl-tree-item>` for details.\n\n### Fallback Content in Slots\n\nWhen providing fallback content inside of `<slot>` elements, avoid adding parts, e.g.:\n\n```html\n<slot name=\"icon\">\n  <sl-icon part=\"close-icon\"></sl-icon>\n</slot>\n```\n\nThis creates confusion because the part will be documented, but it won't work when the user slots in their own content. The recommended way to customize this example is for the user to slot in their own content and target its styles with CSS as needed.\n\n### Custom Events\n\nComponents must only emit custom events, and all custom events must start with `sl-` as a namespace. For compatibility with frameworks that utilize DOM templates, custom events must have lowercase, kebab-style names. For example, use `sl-change` instead of `slChange`.\n\nThis convention avoids the problem of browsers lowercasing attributes, causing some frameworks to be unable to listen to them. This problem isn't specific to one framework, but [Vue's documentation](https://vuejs.org/v2/guide/components-custom-events.html#Event-Names) provides a good explanation of the problem.\n\n### Change Events\n\nWhen change events are emitted by Shoelace components, they should be named `sl-change` and they should only be emitted as a result of user input. Programmatic changes, such as setting `el.value = '…'` _should not_ result in a change event being emitted. This is consistent with how native form controls work.\n\n### CSS Custom Properties\n\nTo expose custom properties as part of a component's API, scope them to the `:host` block.\n\n```css\n:host {\n  --color: var(--sl-color-primary-500);\n  --background-color: var(--sl-color-neutral-100);\n}\n```\n\nThen use the following syntax for comments so they appear in the generated docs. Do not use the `--sl-` prefix, as that is reserved for design tokens that live in the global scope.\n\n```js\n/**\n * @cssproperty --color: The component's text color.\n * @cssproperty --background-color: The component's background color.\n */\nexport default class SlExample {\n  // ...\n}\n```\n\n### Focusing on Disabled Items\n\nWhen an item within a keyboard navigable set is disabled (e.g. tabs, trees, menu items, etc.), the disabled item _should not_ receive focus via keyboard, click, or tap. It should be skipped just like in operating system menus and in native HTML form controls. There is no exception to this. If a particular item requires focus for assistive devices to provide a good user experience, the item should not be disabled and, upon activation, it should inform the user why the respective action cannot be completed.\n\n### When to use a property vs. a CSS custom property\n\nWhen designing a component's API, standard properties are generally used to change the _behavior_ of a component, whereas CSS custom properties (\"CSS variables\") are used to change the _appearance_ of a component. Remember that properties can't respond to media queries, but CSS variables can.\n\nThere are some exceptions to this (e.g. when it significantly improves developer experience), but a good rule of thumbs is \"will this need to change based on screen size?\" If so, you probably want to use a CSS variable.\n\n### When to use a CSS custom property vs. a CSS part\n\nThere are two ways to enable customizations for components. One way is with CSS custom properties (\"CSS variables\"), the other is with CSS parts (\"parts\").\n\nCSS variables are scoped to the host element and can be reused throughout the component. A good example of a CSS variable would be `--border-width`, which might get reused throughout a component to ensure borders share the same width for all internal elements.\n\nParts let you target a specific element inside the component's shadow DOM but, by design, you can't target a part's children or siblings. You can _only_ customize the part itself. Use a part when you need to allow a single element inside the component to accept styles.\n\nThis convention can be relaxed when the developer experience is greatly improved by not following these suggestions.\n\n### Naming CSS Parts\n\nWhile CSS parts can be named [virtually anything](https://www.abeautifulsite.net/posts/valid-names-for-css-parts/), within Shoelace they must use the kebab-case convention and lowercase letters. Additionally, [a BEM-inspired naming convention](https://www.abeautifulsite.net/posts/css-parts-inspired-by-bem/) is used to distinguish parts, subparts, and states.\n\nWhen composing elements, use `part` to export the host element and `exportparts` to export its parts.\n\n```js\nrender() {\n  return html`\n    <div part=\"base\">\n      <sl-icon part=\"icon\" exportparts=\"base:icon__base\" ...></sl-icon>\n    </div>\n  `;\n}\n```\n\nThis results in a consistent, easy to understand structure for parts. In this example, the `icon` part will target the host element and the `icon__base` part will target the icon's `base` part.\n\n### Dependencies\n\nTL;DR – a component is a dependency if and only if it's rendered inside another component's shadow root.\n\nMany Shoelace components use other Shoelace components internally. For example, `<sl-button>` uses both `<sl-icon>` and `<sl-spinner>` for its caret icon and loading state, respectively. Since these components appear in the button's shadow root, they are considered dependencies of Button. Since dependencies are automatically loaded, users only need to import the button and everything will work as expected.\n\nContrast this to `<sl-select>` and `<sl-option>`. At first, one might assume that Option is a dependency of Select. After all, you can't really use Select without slotting in at least one Option. However, Option _is not_ a dependency of Select! The reason is because no Option is rendered in the Select's shadow root. Since the options are provided by the user, it's up to them to import both components independently.\n\nPeople often suggest that Shoelace should auto-load Select + Option, Menu + Menu Item, Breadcrumb + Breadcrumb Item, etc. Although some components are designed to work together, they're technically not dependencies so eagerly loading them may not be desirable. What if someone wants to roll their own component with a superset of features? They wouldn't be able to if Shoelace automatically imported it!\n\nSimilarly, in the case of `<sl-radio-group>` there was originally only `<sl-radio>`, but now you can use either `<sl-radio>` or `<sl-radio-button>` as child elements. Which component(s) should be auto-loaded dependencies in this case? Had Radio been a dependency of Radio Group, users that only wanted Radio Buttons would be forced to register both with no way to opt out and no way to provide their own customized version.\n\nFor non-dependencies, _the user_ should decide what gets registered, even if it comes with a minor inconvenience.\n\n### Form Controls\n\nForm controls should support submission and validation through the following conventions:\n\n- All form controls must use `name`, `value`, and `disabled` properties in the same manner as `HTMLInputElement`\n- All form controls must have a `setCustomValidity()` method so the user can set a custom validation message\n- All form controls must have a `reportValidity()` method that report their validity during form submission\n- All form controls must have an `invalid` property that reflects their validity\n- All form controls should mirror their native validation attributes such as `required`, `pattern`, `minlength`, `maxlength`, etc. when possible\n- All form controls must be tested to work with the standard `<form>` element\n\n### System Icons\n\nAvoid inlining SVG icons inside of templates. If a component requires an icon, make sure `<sl-icon>` is a dependency of the component and use the [system library](/components/icon#customizing-the-system-library):\n\n```html\n<sl-icon library=\"system\" name=\"...\"></sl-icon>\n```\n\nThis will render the icons instantly whereas the default library will fetch them from a remote source. If an icon isn't available in the system library, you will need to add it to `library.system.ts`. Using the system library ensures that all icons load instantly and are customizable by users who wish to provide a custom resolver for the system library.\n\n### Writing tests\n\nWhat to test for a given component:\n\n- Start with a simple test that checks that the default version of the component still renders.\n- Add at least one accessibility test (The accessibility check only covers the parts of the DOM which are currently visible and rendered. Depending on the component, more than one accessibility test is required to cover all scenarios.):\n\n```ts\nconst myComponent = await fixture<SlAlert>(html`<sl-my-component>SomeContent</sl-my-component>`);\n\nawait expect(myComponent).to.be.accessible();\n```\n\n- Try to cover all features advertised in the component's description\n\nGuidelines for writing tests:\n\n- Each test should declare its own, hand crafted hml fixture for the component. Do not try to write one big component to match all tests. This helps keeping each test understandable in isolation.\n- Tests should not produce log lines. Note that sometimes this cannot be prevented as the test runner might log errors (e.g. 404s).\n- Try keeping the main test readable: Extract more complicated sets of selectors/commands/assertions into separate functions.\n- Try to aim testing the user facing features of the component instead of the internal workings of the component.\n- Group multiple tests for one feature into describe blocks.\n"
  },
  {
    "path": "docs/pages/tokens/border-radius.md",
    "content": "---\nmeta:\n  title: Border Radius\n  description: Border radius tokens are used to give sharp edges a more subtle, rounded effect.\n---\n\n# Border Radius Tokens\n\nBorder radius tokens are used to give sharp edges a more subtle, rounded effect. They use rem units so they scale with the base font size. The pixel values displayed are based on a 16px font size.\n\n| Token                        | Value           | Example                                                                                                  |\n| ---------------------------- | --------------- | -------------------------------------------------------------------------------------------------------- |\n| `--sl-border-radius-small`   | 0.1875rem (3px) | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-small);\"></div>             |\n| `--sl-border-radius-medium`  | 0.25rem (4px)   | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-medium);\"></div>            |\n| `--sl-border-radius-large`   | 0.5rem (8px)    | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-large);\"></div>             |\n| `--sl-border-radius-x-large` | 1rem (16px)     | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-x-large);\"></div>           |\n| `--sl-border-radius-circle`  | 50%             | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-circle);\"></div>            |\n| `--sl-border-radius-pill`    | 9999px          | <div class=\"border-radius-demo\" style=\"border-radius: var(--sl-border-radius-pill); width: 6rem;\"></div> |\n"
  },
  {
    "path": "docs/pages/tokens/color.md",
    "content": "---\nmeta:\n  title: Color Tokens\n  description: Color tokens help maintain consistent use of color throughout your app.\n---\n\n# Color Tokens\n\nColor tokens help maintain consistent use of color throughout your app. Shoelace provides palettes for theme colors and primitives that you can use as a foundation for your design system.\n\nColor tokens are referenced using the `--sl-color-{name}-{n}` CSS custom property, where `{name}` is the name of the palette and `{n}` is the numeric value of the desired swatch.\n\n## Theme Tokens\n\nTheme tokens give you a semantic way to reference colors in your app. The primary palette is typically based on a brand color, whereas success, neutral, warning, and danger are used to visualize actions that correspond to their respective meanings.\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Primary<br>\n    <code>--sl-color-primary-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-primary-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Success<br>\n    <code>--sl-color-success-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-success-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Warning<br>\n    <code>--sl-color-warning-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-warning-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Danger<br>\n    <code>--sl-color-danger-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-danger-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Neutral<br>\n    <code>--sl-color-neutral-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-neutral-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Black & White<br>\n    <code>--sl-color-neutral-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch color-palette__swatch--border\" style=\"background-color: var(--sl-color-neutral-0);\"></div>0</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch \" style=\"background-color: var(--sl-color-neutral-1000);\"></div>1000</div>\n</div>\n\n:::tip\nLooking for an easy way to customize your theme? [Try the color token generator!](https://codepen.io/claviska/full/QWveRgL)\n:::\n\n## Primitives\n\nAdditional palettes are provided in the form of color primitives. Use these when you need arbitrary colors that don't have semantic meaning. Color primitives are derived from the fantastic [Tailwind color palette](https://tailwindcss.com/docs/customizing-colors).\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Gray<br>\n    <code>--sl-color-gray-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-900);\"></div>900</div>  \n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-gray-950);\"></div>950</div>  \n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Red<br>\n    <code>--sl-color-red-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-red-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Orange<br>\n    <code>--sl-color-orange-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-orange-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Amber<br>\n    <code>--sl-color-amber-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-amber-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Yellow<br>\n    <code>--sl-color-yellow-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-yellow-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Lime<br>\n    <code>--sl-color-lime-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-lime-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Green<br>\n    <code>--sl-color-green-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-green-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Emerald<br>\n    <code>--sl-color-emerald-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-emerald-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Teal<br>\n    <code>--sl-color-teal-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-teal-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Cyan<br>\n    <code>--sl-color-cyan-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-cyan-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Sky<br>\n    <code>--sl-color-sky-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-sky-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Blue<br>\n    <code>--sl-color-blue-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-blue-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Indigo<br>\n    <code>--sl-color-indigo-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-indigo-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Violet<br>\n    <code>--sl-color-violet-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-violet-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Purple<br>\n    <code>--sl-color-purple-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-purple-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Fuchsia<br>\n    <code>--sl-color-fuchsia-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-fuchsia-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Pink<br>\n    <code>--sl-color-pink-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-pink-950);\"></div>950</div>\n</div>\n\n<div class=\"color-palette\">\n  <div class=\"color-palette__name\">\n    Rose<br>\n    <code>--sl-color-rose-<em>{n}</em></code>\n  </div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-50);\"></div>50</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-100);\"></div>100</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-200);\"></div>200</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-300);\"></div>300</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-400);\"></div>400</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-500);\"></div>500</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-600);\"></div>600</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-700);\"></div>700</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-800);\"></div>800</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-900);\"></div>900</div>\n  <div class=\"color-palette__example\"><div class=\"color-palette__swatch\" style=\"background-color: var(--sl-color-rose-950);\"></div>950</div>\n</div>\n"
  },
  {
    "path": "docs/pages/tokens/elevation.md",
    "content": "---\nmeta:\n  title: Elevation\n  description: Elevation tokens are used to give elements the appearance of being raised off the page.\n---\n\n# Elevation Tokens\n\nElevation tokens are used to give elements the appearance of being raised off the page. Use them with the `box-shadow` property. These are especially useful for menus, popovers, and dialogs.\n\n| Token                 | Example                                                                          |\n| --------------------- | -------------------------------------------------------------------------------- |\n| `--sl-shadow-x-small` | <div class=\"elevation-demo\" style=\"box-shadow: var(--sl-shadow-x-small);\"></div> |\n| `--sl-shadow-small`   | <div class=\"elevation-demo\" style=\"box-shadow: var(--sl-shadow-small);\"></div>   |\n| `--sl-shadow-medium`  | <div class=\"elevation-demo\" style=\"box-shadow: var(--sl-shadow-medium);\"></div>  |\n| `--sl-shadow-large`   | <div class=\"elevation-demo\" style=\"box-shadow: var(--sl-shadow-large);\"></div>   |\n| `--sl-shadow-x-large` | <div class=\"elevation-demo\" style=\"box-shadow: var(--sl-shadow-x-large);\"></div> |\n"
  },
  {
    "path": "docs/pages/tokens/more.md",
    "content": "---\nmeta:\n  title: More Design Tokens\n  description: Additional design tokens can be found here.\n---\n\n# More Design Tokens\n\nAll of the design tokens described herein are considered relatively stable. However, some changes might occur in future versions to address mission critical bugs or improvements. If such changes occur, they _will not_ be considered breaking changes and will be clearly documented in the [changelog](/resources/changelog).\n\nMost design tokens are consistent across the light and dark theme. Those that vary will show both values.\n\n:::tip\nCurrently, the source of design tokens is considered to be [`light.css`](https://github.com/shoelace-style/shoelace/blob/next/src/themes/light.css). The dark theme, [dark.css](https://github.com/shoelace-style/shoelace/blob/next/src/themes/dark.css), mirrors all of the same tokens with dark mode-specific values where appropriate. Work is planned to move all design tokens to a single file, perhaps JSON or YAML, in the near future.\n:::\n\n## Focus Rings\n\nFocus ring tokens control the appearance of focus rings. Note that form inputs use `--sl-input-focus-ring-*` tokens instead.\n\n| Token                    | Value                                                                                     |\n| ------------------------ | ----------------------------------------------------------------------------------------- |\n| `--sl-focus-ring-color`  | `var(--sl-color-primary-600)` (light theme)<br>`var(--sl-color-primary-700)` (dark theme) |\n| `--sl-focus-ring-style`  | `solid`                                                                                   |\n| `--sl-focus-ring-width`  | `3px`                                                                                     |\n| `--sl-focus-ring`        | `var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color)`        |\n| `--sl-focus-ring-offset` | `1px`                                                                                     |\n\n## Buttons\n\nButton tokens control the appearance of buttons. In addition, buttons also currently use some form input tokens such as `--sl-input-height-*` and `--sl-input-border-*`. More button tokens may be added in the future to make it easier to style them more independently.\n\n| Token                          | Value                         |\n| ------------------------------ | ----------------------------- |\n| `--sl-button-font-size-small`  | `var(--sl-font-size-x-small)` |\n| `--sl-button-font-size-medium` | `var(--sl-font-size-small)`   |\n| `--sl-button-font-size-large`  | `var(--sl-font-size-medium)`  |\n\n## Form Inputs\n\nForm input tokens control the appearance of form controls such as [input](/components/input), [select](/components/select), [textarea](/components/textarea), etc.\n\n| Token                                   | Value                              |\n| --------------------------------------- | ---------------------------------- |\n| `--sl-input-height-small`               | `1.875rem` (30px @ 16px base)      |\n| `--sl-input-height-medium`              | `2.5rem` (40px @ 16px base)        |\n| `--sl-input-height-large`               | `3.125rem` (50px @ 16px base)      |\n| `--sl-input-background-color`           | `var(--sl-color-neutral-0)`        |\n| `--sl-input-background-color-hover`     | `var(--sl-input-background-color)` |\n| `--sl-input-background-color-focus`     | `var(--sl-input-background-color)` |\n| `--sl-input-background-color-disabled`  | `var(--sl-color-neutral-100)`      |\n| `--sl-input-border-color`               | `var(--sl-color-neutral-300)`      |\n| `--sl-input-border-color-hover`         | `var(--sl-color-neutral-400)`      |\n| `--sl-input-border-color-focus`         | `var(--sl-color-primary-500)`      |\n| `--sl-input-border-color-disabled`      | `var(--sl-color-neutral-300)`      |\n| `--sl-input-border-width`               | `1px`                              |\n| `--sl-input-required-content`           | `*`                                |\n| `--sl-input-required-content-offset`    | `-2px`                             |\n| `--sl-input-required-content-color`     | `var(--sl-input-label-color)`      |\n| `--sl-input-border-radius-small`        | `var(--sl-border-radius-medium)`   |\n| `--sl-input-border-radius-medium`       | `var(--sl-border-radius-medium)`   |\n| `--sl-input-border-radius-large`        | `var(--sl-border-radius-medium)`   |\n| `--sl-input-font-family`                | `var(--sl-font-sans)`              |\n| `--sl-input-font-weight`                | `var(--sl-font-weight-normal)`     |\n| `--sl-input-font-size-small`            | `var(--sl-font-size-small)`        |\n| `--sl-input-font-size-medium`           | `var(--sl-font-size-medium)`       |\n| `--sl-input-font-size-large`            | `var(--sl-font-size-large)`        |\n| `--sl-input-letter-spacing`             | `var(--sl-letter-spacing-normal)`  |\n| `--sl-input-color`                      | `var(--sl-color-neutral-700)`      |\n| `--sl-input-color-hover`                | `var(--sl-color-neutral-700)`      |\n| `--sl-input-color-focus`                | `var(--sl-color-neutral-700)`      |\n| `--sl-input-color-disabled`             | `var(--sl-color-neutral-900)`      |\n| `--sl-input-icon-color`                 | `var(--sl-color-neutral-500)`      |\n| `--sl-input-icon-color-hover`           | `var(--sl-color-neutral-600)`      |\n| `--sl-input-icon-color-focus`           | `var(--sl-color-neutral-600)`      |\n| `--sl-input-placeholder-color`          | `var(--sl-color-neutral-500)`      |\n| `--sl-input-placeholder-color-disabled` | `var(--sl-color-neutral-600)`      |\n| `--sl-input-spacing-small`              | `var(--sl-spacing-small)`          |\n| `--sl-input-spacing-medium`             | `var(--sl-spacing-medium)`         |\n| `--sl-input-spacing-large`              | `var(--sl-spacing-large)`          |\n| `--sl-input-focus-ring-color`           | `hsl(198.6 88.7% 48.4% / 40%)`     |\n| `--sl-input-focus-ring-offset`          | `0`                                |\n\n## Filled Form Inputs\n\nFilled form input tokens control the appearance of form controls using the `filled` variant.\n\n| Token                                         | Value                         |\n| --------------------------------------------- | ----------------------------- |\n| `--sl-input-filled-background-color`          | `var(--sl-color-neutral-100)` |\n| `--sl-input-filled-background-color-hover`    | `var(--sl-color-neutral-100)` |\n| `--sl-input-filled-background-color-focus`    | `var(--sl-color-neutral-100)` |\n| `--sl-input-filled-background-color-disabled` | `var(--sl-color-neutral-100)` |\n| `--sl-input-filled-color`                     | `var(--sl-color-neutral-800)` |\n| `--sl-input-filled-color-hover`               | `var(--sl-color-neutral-800)` |\n| `--sl-input-filled-color-focus`               | `var(--sl-color-neutral-700)` |\n| `--sl-input-filled-color-disabled`            | `var(--sl-color-neutral-800)` |\n\n## Form Labels\n\nForm label tokens control the appearance of labels in form controls.\n\n| Token                               | Value                        |\n| ----------------------------------- | ---------------------------- |\n| `--sl-input-label-font-size-small`  | `var(--sl-font-size-small)`  |\n| `--sl-input-label-font-size-medium` | `var(--sl-font-size-medium`) |\n| `--sl-input-label-font-size-large`  | `var(--sl-font-size-large)`  |\n| `--sl-input-label-color`            | `inherit`                    |\n\n## Help Text\n\nHelp text tokens control the appearance of help text in form controls.\n\n| Token                                   | Value                         |\n| --------------------------------------- | ----------------------------- |\n| `--sl-input-help-text-font-size-small`  | `var(--sl-font-size-x-small)` |\n| `--sl-input-help-text-font-size-medium` | `var(--sl-font-size-small)`   |\n| `--sl-input-help-text-font-size-large`  | `var(--sl-font-size-medium)`  |\n| `--sl-input-help-text-color`            | `var(--sl-color-neutral-500)` |\n\n## Toggles\n\nToggle tokens control the appearance of toggles such as [checkbox](/components/checkbox), [radio](/components/radio), [switch](/components/switch), etc.\n\n| Token                     | Value                         |\n| ------------------------- | ----------------------------- |\n| `--sl-toggle-size-small`  | `0.875rem` (14px @ 16px base) |\n| `--sl-toggle-size-medium` | `1.125rem` (18px @ 16px base) |\n| `--sl-toggle-size-large`  | `1.375rem` (22px @ 16px base) |\n\n## Overlays\n\nOverlay tokens control the appearance of overlays as used in [dialog](/components/dialog), [drawer](/components/drawer), etc.\n\n| Token                           | Value                       |\n| ------------------------------- | --------------------------- |\n| `--sl-overlay-background-color` | `hsl(240 3.8% 46.1% / 33%)` |\n\n## Panels\n\nPanel tokens control the appearance of panels such as those used in [dialog](/components/dialog), [drawer](/components/drawer), [menu](/components/menu), etc.\n\n| Token                         | Value                         |\n| ----------------------------- | ----------------------------- |\n| `--sl-panel-background-color` | `var(--sl-color-neutral-0)`   |\n| `--sl-panel-border-color`     | `var(--sl-color-neutral-200)` |\n| `--sl-panel-border-width`     | `1px`                         |\n\n## Tooltips\n\nTooltip tokens control the appearance of tooltips. This includes the [tooltip](/components/tooltip) component as well as other implementations, such [range tooltips](/components/range).\n\n| Token                           | Value                                                  |\n| ------------------------------- | ------------------------------------------------------ |\n| `--sl-tooltip-border-radius`    | `var(--sl-border-radius-medium)`                       |\n| `--sl-tooltip-background-color` | `var(--sl-color-neutral-800)`                          |\n| `--sl-tooltip-color`            | `var(--sl-color-neutral-0)`                            |\n| `--sl-tooltip-font-family`      | `var(--sl-font-sans)`                                  |\n| `--sl-tooltip-font-weight`      | `var(--sl-font-weight-normal)`                         |\n| `--sl-tooltip-font-size`        | `var(--sl-font-size-small)`                            |\n| `--sl-tooltip-line-height`      | `var(--sl-line-height-dense)`                          |\n| `--sl-tooltip-padding`          | `var(--sl-spacing-2x-small) var(--sl-spacing-x-small)` |\n| `--sl-tooltip-arrow-size`       | `6px`                                                  |\n"
  },
  {
    "path": "docs/pages/tokens/spacing.md",
    "content": "---\nmeta:\n  title: Spacing Tokens\n  description: Spacing tokens are used to provide consistent spacing between content in your app.\n---\n\n# Spacing Tokens\n\nSpacing tokens are used to provide consistent spacing between content in your app.\n\n| Token                   | Value          | Example                                                                                                         |\n| ----------------------- | -------------- | --------------------------------------------------------------------------------------------------------------- |\n| `--sl-spacing-3x-small` | 0.125rem (2px) | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-3x-small); height: var(--sl-spacing-3x-small);\"></div> |\n| `--sl-spacing-2x-small` | 0.25rem (4px)  | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-2x-small); height: var(--sl-spacing-2x-small);\"></div> |\n| `--sl-spacing-x-small`  | 0.5rem (8px)   | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-x-small); height: var(--sl-spacing-x-small);\"></div>   |\n| `--sl-spacing-small`    | 0.75rem (12px) | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-small); height: var(--sl-spacing-small);\"></div>       |\n| `--sl-spacing-medium`   | 1rem (16px)    | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-medium); height: var(--sl-spacing-medium);\"></div>     |\n| `--sl-spacing-large`    | 1.25rem (20px) | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-large); height: var(--sl-spacing-large);\"></div>       |\n| `--sl-spacing-x-large`  | 1.75rem (28px) | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-x-large); height: var(--sl-spacing-x-large);\"></div>   |\n| `--sl-spacing-2x-large` | 2.25rem (36px) | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-2x-large); height: var(--sl-spacing-2x-large);\"></div> |\n| `--sl-spacing-3x-large` | 3rem (48px)    | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-3x-large); height: var(--sl-spacing-3x-large);\"></div> |\n| `--sl-spacing-4x-large` | 4.5rem (72px)  | <div class=\"spacing-demo\" style=\"width: var(--sl-spacing-4x-large); height: var(--sl-spacing-4x-large);\"></div> |\n"
  },
  {
    "path": "docs/pages/tokens/transition.md",
    "content": "---\nmeta:\n  title: Transition Tokens\n  description: Transition tokens are used to provide consistent transitions throughout your app.\n---\n\n# Transition Tokens\n\nTransition tokens are used to provide consistent transitions throughout your app.\n\n| Token                    | Value  | Example                                                                                       |\n| ------------------------ | ------ | --------------------------------------------------------------------------------------------- |\n| `--sl-transition-x-slow` | 1000ms | <div class=\"transition-demo\" style=\"transition-duration: var(--sl-transition-x-slow);\"></div> |\n| `--sl-transition-slow`   | 500ms  | <div class=\"transition-demo\" style=\"transition-duration: var(--sl-transition-slow);\"></div>   |\n| `--sl-transition-medium` | 250ms  | <div class=\"transition-demo\" style=\"transition-duration: var(--sl-transition-medium);\"></div> |\n| `--sl-transition-fast`   | 150ms  | <div class=\"transition-demo\" style=\"transition-duration: var(--sl-transition-fast);\"></div>   |\n| `--sl-transition-x-fast` | 50ms   | <div class=\"transition-demo\" style=\"transition-duration: var(--sl-transition-x-fast);\"></div> |\n"
  },
  {
    "path": "docs/pages/tokens/typography.md",
    "content": "---\nmeta:\n  title: Typography\n  description: Typography tokens are used to maintain a consistent set of font styles throughout your app.\n---\n\n# Typography Tokens\n\nTypography tokens are used to maintain a consistent set of font styles throughout your app.\n\n## Font Family\n\nThe default font stack is designed to be simple and highly available on as many devices as possible.\n\n| Token             | Value                                                                                                                                         | Example                                                                                              |\n| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |\n| `--sl-font-sans`  | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' | <span style=\"font-family: var(--sl-font-sans)\">The quick brown fox jumped over the lazy dog.</span>  |\n| `--sl-font-serif` | Georgia, 'Times New Roman', serif                                                                                                             | <span style=\"font-family: var(--sl-font-serif)\">The quick brown fox jumped over the lazy dog.</span> |\n| `--sl-font-mono`  | SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;                                                                                | <span style=\"font-family: var(--sl-font-mono)\">The quick brown fox jumped over the lazy dog.</span>  |\n\n## Font Size\n\nFont sizes use `rem` units so they scale with the base font size. The pixel values displayed are based on a 16px font size.\n\n| Token                     | Value           | Example                                                         |\n| ------------------------- | --------------- | --------------------------------------------------------------- |\n| `--sl-font-size-2x-small` | 0.625rem (10px) | <span style=\"font-size: var(--sl-font-size-2x-small)\">Aa</span> |\n| `--sl-font-size-x-small`  | 0.75rem (12px)  | <span style=\"font-size: var(--sl-font-size-x-small)\">Aa</span>  |\n| `--sl-font-size-small`    | 0.875rem (14px) | <span style=\"font-size: var(--sl-font-size-small)\">Aa</span>    |\n| `--sl-font-size-medium`   | 1rem (16px)     | <span style=\"font-size: var(--sl-font-size-medium)\">Aa</span>   |\n| `--sl-font-size-large`    | 1.25rem (20px)  | <span style=\"font-size: var(--sl-font-size-large)\">Aa</span>    |\n| `--sl-font-size-x-large`  | 1.5rem (24px)   | <span style=\"font-size: var(--sl-font-size-x-large)\">Aa</span>  |\n| `--sl-font-size-2x-large` | 2.25rem (36px)  | <span style=\"font-size: var(--sl-font-size-2x-large)\">Aa</span> |\n| `--sl-font-size-3x-large` | 3rem (48px)     | <span style=\"font-size: var(--sl-font-size-3x-large)\">Aa</span> |\n| `--sl-font-size-4x-large` | 4.5rem (72px)   | <span style=\"font-size: var(--sl-font-size-4x-large)\">Aa</span> |\n\n## Font Weight\n\n| Token                       | Value | Example                                                                                                         |\n| --------------------------- | ----- | --------------------------------------------------------------------------------------------------------------- |\n| `--sl-font-weight-light`    | 300   | <span style=\"font-weight: var(--sl-font-weight-light);\">The quick brown fox jumped over the lazy dog.</span>    |\n| `--sl-font-weight-normal`   | 400   | <span style=\"font-weight: var(--sl-font-weight-normal);\">The quick brown fox jumped over the lazy dog.</span>   |\n| `--sl-font-weight-semibold` | 500   | <span style=\"font-weight: var(--sl-font-weight-semibold);\">The quick brown fox jumped over the lazy dog.</span> |\n| `--sl-font-weight-bold`     | 700   | <span style=\"font-weight: var(--sl-font-weight-bold);\">The quick brown fox jumped over the lazy dog.</span>     |\n\n## Letter Spacing\n\n| Token                        | Value    | Example                                                                                                             |\n| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |\n| `--sl-letter-spacing-denser` | -0.03em  | <span style=\"letter-spacing: var(--sl-letter-spacing-denser);\">The quick brown fox jumped over the lazy dog.</span> |\n| `--sl-letter-spacing-dense`  | -0.015em | <span style=\"letter-spacing: var(--sl-letter-spacing-dense);\">The quick brown fox jumped over the lazy dog.</span>  |\n| `--sl-letter-spacing-normal` | normal   | <span style=\"letter-spacing: var(--sl-letter-spacing-normal);\">The quick brown fox jumped over the lazy dog.</span> |\n| `--sl-letter-spacing-loose`  | 0.075em  | <span style=\"letter-spacing: var(--sl-letter-spacing-loose);\">The quick brown fox jumped over the lazy dog.</span>  |\n| `--sl-letter-spacing-looser` | 0.15em   | <span style=\"letter-spacing: var(--sl-letter-spacing-looser);\">The quick brown fox jumped over the lazy dog.</span> |\n\n## Line Height\n\n| Token                     | Value | Example                                                                                                                                                                                                       |\n| ------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `--sl-line-height-denser` | 1     | <div style=\"line-height: var(--sl-line-height-denser);\">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |\n| `--sl-line-height-dense`  | 1.4   | <div style=\"line-height: var(--sl-line-height-dense);\">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div>  |\n| `--sl-line-height-normal` | 1.8   | <div style=\"line-height: var(--sl-line-height-normal);\">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |\n| `--sl-line-height-loose`  | 2.2   | <div style=\"line-height: var(--sl-line-height-loose);\">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div>  |\n| `--sl-line-height-looser` | 2.6   | <div style=\"line-height: var(--sl-line-height-looser);\">The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.<br>The quick brown fox jumped over the lazy dog.</div> |\n"
  },
  {
    "path": "docs/pages/tokens/z-index.md",
    "content": "---\nmeta:\n  title: Z-Index Tokens\n  description: Z-indexes are used to stack components in a logical manner.\n---\n\n# Z-Index Tokens\n\nZ-indexes are used to stack components in a logical manner.\n\n| Token                   | Value |\n| ----------------------- | ----- |\n| `--sl-z-index-drawer`   | 700   |\n| `--sl-z-index-dialog`   | 800   |\n| `--sl-z-index-dropdown` | 900   |\n| `--sl-z-index-toast`    | 950   |\n| `--sl-z-index-tooltip`  | 1000  |\n"
  },
  {
    "path": "docs/pages/tutorials/integrating-with-astro.md",
    "content": "---\nmeta:\n  title: Integrating with Astro\n  description: This page explains how to integrate Shoelace with an Astro app.\n---\n\n# Integrating with Astro\n\nThis page explains how to integrate Shoelace with an Astro app.\n\n:::tip\nThis is a community-maintained document. Please [ask the community](/resources/community) if you have questions about this integration. You can also [suggest improvements](https://github.com/shoelace-style/shoelace/blob/next/docs/tutorials/integrating-with-astro.md) to make it better.\n:::\n\n## SSR and client scripts\n\nIn the following tutorial you will notice that Shoelace cannot be imported in the frontmatter of Astro files. This is because Shoelace relies on globals from the DOM to be present.\n\nThere is a [Lit + Astro integration for SSR](https://docs.astro.build/en/guides/integrations-guide/lit/) , however it will not work with Shoelace in its current state due to some reliance on DOM APIs that aren't shimmed. We are working to resolve this.\n\n## Instructions - Astro 4.11.3\n\n- Node: v18.17.1 or v20.3.0 or higher. ( v19 is not supported.)\n- Astro: 4.11.3\n- Shoelace: 2.15.1\n\nTo get started using Shoelace with Astro, the following packages must be installed.\n\n```bash\nnpm install @shoelace-style/shoelace rollup-plugin-copy\n```\n\n### Importing components\n\nIn `/src/pages/index.astro`, set the base path and import Shoelace.\n\n```html\n---\n// import default stylesheet\nimport \"@shoelace-style/shoelace/dist/themes/light.css\";\n---\n\n<html>\n  <body>\n    <sl-button>Button</sl-button>\n  </body>\n</html>\n\n<script>\n  // setBasePath to tell Shoelace where to load icons from.\n  import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';\n  setBasePath('/shoelace-assets/');\n\n  // Load all components.\n  import '@shoelace-style/shoelace';\n</script>\n```\n\n:::tip\nIf you want to cherry pick components, replace the main Shoelace import with 'import \"@shoelace-style/shoelace/dist/components/button/button.js\";' for whichever component you would like.\n:::\n\nYou only have to import in the main `index.astro` file. The components can be used anywhere throughout the project.\n\n### Customizing animations\n\nIn `/src/pages/index.astro`, set custom animations after the Shoelace import.\n\n```html\n---\nimport { setBasePath } from \"@shoelace-style/shoelace/dist/utilities/base-path.js\";\nsetBasePath(\"dist/assets\");\n---\n\n<html>\n  <body>\n    <sl-tooltip content=\"This is a tooltip\">\n      <sl-button>Hover Me</sl-button>\n    </sl-tooltip>\n  </body>\n</html>\n\n<script>\n  // setBasePath to tell Shoelace where to load icons from.\n  import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';\n  setBasePath('/shoelace-assets/');\n\n  // Load all components.\n  import '@shoelace-style/shoelace';\n\n  const duration = 3000;\n  import { setDefaultAnimation } from '@shoelace-style/shoelace/dist/utilities/animation-registry.js';\n\n  setDefaultAnimation('tooltip.show', {\n    keyframes: [\n      { opacity: 0, scale: 0.8 },\n      { opacity: 1, scale: 1 }\n    ],\n    options: { duration: duration, easing: 'ease' }\n  });\n</script>\n```\n\n## Modifying Astro Config\n\nYou'll notice the above steps never added our icons into our `/public` directory. To solve this, we can install `rollup-plugin-copy` to copy Shoelace's assets into your public directory.\n\nHere's what your Astro config should look like:\n\n```js\n// astro.config.mjs\n\nimport { defineConfig } from 'astro/config';\nimport copy from 'rollup-plugin-copy';\n\n// https://astro.build/config\nexport default defineConfig({\n  vite: {\n    plugins: [\n      copy({\n        // Copy only on first build. We dont want to trigger additional server reloads.\n        copyOnce: true,\n        hook: 'buildStart',\n        targets: [\n          { src: 'node_modules/@shoelace-style/shoelace/dist/assets/*', dest: 'public/shoelace-assets/assets/' }\n        ]\n      })\n    ]\n  }\n});\n```\n"
  },
  {
    "path": "docs/pages/tutorials/integrating-with-laravel.md",
    "content": "---\nmeta:\n  title: Integrating with Laravel\n  description: This page explains how to integrate Shoelace with a Laravel app.\n---\n\n# Integrating with Laravel\n\nThis page explains how to integrate Shoelace with a [Laravel 9](https://laravel.com) app using Vite. For additional details refer to the [Bundling Assets (Vite)](https://laravel.com/docs/9.x/vite) section in the official Laravel docs.\n\n:::tip\nThis is a community-maintained document. Please [ask the community](/resources/community) if you have questions about this integration. You can also [suggest improvements](https://github.com/shoelace-style/shoelace/blob/next/docs/pages/tutorials/integrating-with-laravel.md) to make it better.\n:::\n\n## Requirements\n\nThis integration has been tested with the following:\n\n- Laravel 9.1\n- Vite 3.0\n\n## Instructions\n\nThese instructions assume a default [Laravel 9 install](https://laravel.com/docs/9.x/installation) that uses [Vite](https://vitejs.dev/) to bundle assets.\nBe sure to run `npm install` to install the default Laravel front-end dependencies before installing Shoelace.\n\n### Install the Shoelace package\n\n```bash\nnpm install @shoelace-style/shoelace\n```\n\n### Import the Default Theme\n\nImport the Shoelace default theme (stylesheet) in `/resources/css/app.css`:\n\n```css\n@import '/node_modules/@shoelace-style/shoelace/dist/themes/light.css';\n```\n\n### Import Your Shoelace Components\n\nImport each Shoelace component you plan to use in `/resources/js/bootstrap.js`. Use the full path to each component (as outlined in the [Cherry Picking instructions](https://shoelace.style/getting-started/installation#cherry-picking)). You can find the full import statement for a component in the _Importing_ section of the component's documentation (use the _Bundler_ import). Your imports should look similar to:\n\n```js\nimport '@shoelace-style/shoelace/dist/components/button/button.js';\nimport '@shoelace-style/shoelace/dist/components/icon/icon.js';\nimport '@shoelace-style/shoelace/dist/components/dialog/dialog.js';\n```\n\n### Copy the Shoelace Static Assets (icons, images, etc.) to a Public Folder\n\nSince Vite has no way to copy arbitrary assets into your build (like webpack), you need to manually copy the Shoelace static assets to your project's public folder. Run this command from your project's root directory to copy the Shoelace static assets to the `./public/assets` folder:\n\n```sh\ncp -aR node_modules/@shoelace-style/shoelace/dist/assets/ ./public/assets\n```\n\n### Set the Base Path\n\nAdd the base path to your Shoelace assets (icons, images, etc.) in `/resources/js/bootstrap.js`. The path must point to the same folder where you copy assets to in the next step.\n\n```js\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';\nsetBasePath('/');\n```\n\nExample `/resources/js/bootstrap.js` file:\n\n```js\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';\nsetBasePath('/');\n\nimport '@shoelace-style/shoelace/dist/components/button/button.js';\nimport '@shoelace-style/shoelace/dist/components/icon/icon.js';\nimport '@shoelace-style/shoelace/dist/components/dialog/dialog.js';\n```\n\n### Verify Vite Entry Points\n\nLaravel pre-configures the Vite entry points in `vite.config.js` as `resources/css/app.css` and `resources/js/app.js`. If you use a different location for your CSS and/or Javascript entry point, update this configuration to accordingly.\n\n```js\nplugins: [\n        laravel({\n            input: [\"resources/css/app.css\", \"resources/js/app.js\"],\n            refresh: true,\n        }),\n    ],\n```\n\n### Compile Front-End Assets\n\nRun the Vite development server or production build.\n\n```bash\n## run the Vite development bundle\nnpm run dev\n\n## build a production bundle (with versioning)\nnpm run build\n```\n\n### Include Front-End Assets in Your Layout File\n\nAdd the `@vite()` Blade directive to the `<head>` of your application's root template.\n\n```html\n<head>\n  ... @vite(['resources/css/app.css', 'resources/js/app.js'])\n</head>\n```\n\nHave fun using Shoelace components in your Laravel app!\n"
  },
  {
    "path": "docs/pages/tutorials/integrating-with-nextjs.md",
    "content": "---\nmeta:\n  title: Integrating with NextJS\n  description: This page explains how to integrate Shoelace with a NextJS app.\n---\n\n# Integrating with NextJS\n\nThis page explains how to integrate Shoelace with a NextJS app.\n\n:::tip\nThis is a community-maintained document. Please [ask the community](/resources/community) if you have questions about this integration. You can also [suggest improvements](https://github.com/shoelace-style/shoelace/blob/next/docs/tutorials/integrating-with-nextjs.md) to make it better.\n:::\n\nThere are 2 guides available:\n\n- [Usage with App Router NextJS 14](#usage-with-app-router-nextjs-14)\n- [Usage with Pages Router NextJS 12](#usage-with-pages-router-nextjs-12)\n\n## Usage with App Router (NextJS 14)\n\n- Node: v20.11.1\n- NextJS: 14.2.4\n- Shoelace: 2.15.1\n\n### Working with ESM\n\nIf you haven't already, create your NextJS app. You can find the documentation for that here: <https://nextjs.org/docs/getting-started/installation>\n\nAfter you've created your app, the first step to using Shoelace is modifying your `package.json` to have `\"type\": \"module\"` in it since Shoelace ships ES Modules.\n\n```json\n// package.json\n{\n  \"type\": \"module\"\n}\n```\n\n### Installing packages\n\nTo get started using Shoelace with NextJS, the following packages must be installed.\n\n```bash\nnpm install @shoelace-style/shoelace copy-webpack-plugin\n```\n\nShoelace for obvious reasons, and the `copy-webpack-plugin` will be used later for adding our icons to our `public/` folder.\n\n### Modifying your Next Config\n\nWe'll start with modifying our `next.config.js` to copy Shoelace's assets and to properly work with ESM.\n\nHere's what your `next.config.js` should look like:\n\n### NextJS 14 Webpack Config\n\nIn order to add Shoelace's assets to the final build output, we need to modify `next.config.js` to look like this.\n\n```javascript\n// next.config.js\n\nimport { dirname, resolve } from 'path';\nimport { fileURLToPath } from 'url';\nimport CopyPlugin from 'copy-webpack-plugin';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  experimental: { esmExternals: 'loose' },\n  webpack: (config, options) => {\n    config.plugins.push(\n      new CopyPlugin({\n        patterns: [\n          {\n            from: resolve(__dirname, 'node_modules/@shoelace-style/shoelace/dist/assets/'),\n            to: resolve(__dirname, 'public/shoelace-assets/assets/')\n          }\n        ]\n      })\n    );\n    return config;\n  }\n};\n\nexport default nextConfig;\n```\n\n:::tip\nThis will copy the files from `node_modules/@shoelace-style/shoelace/dist/assets` into your `public/shoelace-assets` folder on every development serve or build. You may want to avoid committing these into your repo. To do so, simply add `public/shoelace-assets` into your `.gitignore` folder\n:::\n\n### Importing the Shoelace's CSS (default theme)\n\nOnce we've got our webpack config / next config setup, lets modify our `app/layout.tsx` to include Shoelace's default theme.\n\n```javascript\n// app/layout.tsx\nimport './globals.css';\nimport '@shoelace-style/shoelace/dist/themes/light.css';\n// We can also import the dark theme here as well.\n// import \"@shoelace-style/shoelace/dist/themes/dark.css\";\n```\n\n### Writing a \"setup\" component\n\nNow, we need to create a `ShoelaceSetup` component that will be a client component in charge of setting the `basePath` for our assets / icons.\n\nTo do so, create a file called `app/shoelace-setup.tsx`\n\n```typescript\n'use client';\n// ^ Make sure to have 'use client'; because `setBasePath()` requires access to `document`.\n\nimport { setBasePath } from \"@shoelace-style/shoelace/dist/utilities/base-path.js\"\n\nexport default function ShoelaceSetup({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  setBasePath(`/shoelace-assets/`);\n  return <>{children}</>\n}\n```\n\n:::warning\nDon't forget to mark your Shoelace components and Shoelace setup with 'use client'.\n:::\n\nThen we'll add this setup component into `app/layout.tsx`\n\nOur `layout.tsx` Should now look something like this:\n\n```diff\nimport type { Metadata } from \"next\";\nimport { Inter } from \"next/font/google\";\nimport \"./globals.css\";\n+ import \"@shoelace-style/shoelace/dist/themes/light.css\";\n\n+ import ShoelaceSetup from \"./shoelace-setup\";\n\nconst inter = Inter({ subsets: [\"latin\"] });\n\nexport const metadata: Metadata = {\n  title: \"Create Next App\",\n  description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\">\n      <body className={inter.className}>\n+         <ShoelaceSetup>\n            {children}\n+         </ShoelaceSetup>\n      </body>\n    </html>\n  );\n}\n```\n\n### Writing components that use Shoelace\n\nNow that we have the setup in place, we can write an `app/page.tsx` to use Shoelace components in combination with the `dynamic()` component loader from NextJS.\n\nHere's what that would look like, do note the `\"use client\";` at the top of the component is required.\n\n```typescript\n// app/page.tsx\n'use client';\n\nimport React from \"react\";\nimport dynamic from \"next/dynamic\";\n\nconst SlButton = dynamic(\n  // Notice how we use the full path to the component. If you only do `import(\"@shoelace-style/shoelace/dist/react\")` you will load the entire component library and not get tree shaking.\n  () => import(\"@shoelace-style/shoelace/dist/react/button/index.js\"),\n  {\n    loading: () => <p>Loading...</p>,\n    ssr: false,\n  },\n);\n\nconst SlIcon = dynamic(\n  () => import(\"@shoelace-style/shoelace/dist/react/icon/index.js\"),\n  {\n    loading: () => <p>Loading...</p>,\n    ssr: false,\n  },\n);\n\nexport default function Home() {\n  return (\n    <main>\n      <SlButton>Test</SlButton>\n      <SlIcon name=\"gear\" />\n    </main>\n  );\n}\n```\n\nNow you should be up and running with NextJS + Shoelace!\n\nIf you're stuck, there's an [example repo here](https://github.com/konnorRogers/shoelace-nextjs-lazy) you can checkout.\n\n## Usage with Pages Router (NextJS 12)\n\n- Node: 16.13.1\n- NextJS: 12.1.6\n- Shoelace: 2.0.0-beta.74\n\nTo get started using Shoelace with NextJS, the following packages must be installed.\n\n```bash\nyarn add @shoelace-style/shoelace copy-webpack-plugin next-compose-plugins next-transpile-modules\n```\n\n### Enabling ESM\n\nBecause Shoelace utilizes ESM, we need to modify our `package.json` to support ESM packages. Simply add the following to\nyour root of `package.json`:\n\n```\n\"type\": \"module\"\n```\n\nThere's one more step to enable ESM in NextJS, but we'll tackle that in our Next configuration modification.\n\n### Importing the Default Theme\n\nThe next step is to import Shoelace's default theme (stylesheet) in your `_app.js` file:\n\n```css\nimport '@shoelace-style/shoelace/dist/themes/light.css';\n```\n\n### Defining Custom Elements\n\nAfter importing the theme, you'll need to import the JavaScript files for Shoelace. However, this is a bit tricky to do in NextJS thanks to the SSR environment not having any of the required browser APIs to define endpoints.\n\nWe'll want to create a component that uses [React's `useLayoutEffect`](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) to add in the custom components before the first render:\n\n```javascript\nfunction CustomEls({ URL }) {\n  // useRef to avoid re-renders\n  const customEls = useRef(false);\n\n  useLayoutEffect(() => {\n    if (customEls.current) {\n      return;\n    }\n\n    import('@shoelace-style/shoelace/dist/utilities/base-path').then(({ setBasePath }) => {\n      setBasePath(`${URL}/static/static`);\n\n      // This imports all components\n      import('@shoelace-style/shoelace/dist/react');\n      // If you're wanting to selectively import components, replace this line with your own definitions\n\n      // import(\"@shoelace-style/shoelace/dist/components/button/button\");\n      customEls.current = true;\n    });\n  }, [URL, customEls]);\n\n  return null;\n}\n```\n\n:::tip\nIf we use `useEffect` instead of `useLayoutEffect`, the initial render will occur with the expected `sl-` props applied, but the subsequent render (caused by the `useEffect`) will remove those props as the custom components initialize. We _must_ use `useLayoutEffect` to have expected behavior\n:::\n\n:::tip\nThis will import all Shoelace components for convenience. To selectively import components, refer to the [Using webpack](/getting-started/installation#using-webpack) section of the docs.\n:::\n\nYou may be wondering where the `URL` property is coming from. We'll address that in the next few sections.\n\n### Using Our New Component In Code\n\nWhile we need to use `useLayoutEffect` for the initial render, NextJS will throw a warning at us for trying to use `useLayoutEffect` in SSR, which is disallowed. To fix this problem, we'll conditionally render the `CustomEls` component to only render in the browser\n\n```javascript\nfunction MyApp({ Component, pageProps, URL }) {\n  const isBrowser = typeof window !== 'undefined';\n  return (\n    <>\n      {isBrowser && <CustomEls URL={URL} />}\n      <Component {...pageProps} />\n    </>\n  );\n}\n```\n\n## Additional Resources\n\n- There is a third-party [example repo](https://github.com/crutchcorn/nextjs-shoelace-example), courtesy of [crutchcorn](https://github.com/crutchcorn), available to help you get started using Pages Router.\n- There is an [example repo](https://github.com/konnorRogers/shoelace-nextjs-lazy) here using the App Router.\n"
  },
  {
    "path": "docs/pages/tutorials/integrating-with-rails.md",
    "content": "---\nmeta:\n  title: Integrating with Rails\n  description: This page explains how to integrate Shoelace with a Rails app.\n---\n\n# Integrating with Rails\n\nThis page explains how to integrate Shoelace with a Rails app.\n\n:::tip\nThis is a community-maintained document. Please [ask the community](/resources/community) if you have questions about this integration. You can also [suggest improvements](https://github.com/shoelace-style/shoelace/blob/next/docs/tutorials/integrating-with-rails.md) to make it better.\n:::\n\n## Requirements\n\nThis integration has been tested with the following:\n\n- Rails >= 6\n- Node >= 12.10\n- Webpacker >= 5\n\n## Instructions\n\nTo get started using Shoelace with Rails, the following packages must be installed.\n\n```bash\nyarn add @shoelace-style/shoelace copy-webpack-plugin\n```\n\n### Importing the Default Theme\n\nThe next step is to import Shoelace's default theme (stylesheet) in `app/javascript/stylesheets/application.scss`.\n\n```css\n@import '@shoelace-style/shoelace/dist/themes/light';\n@import '@shoelace-style/shoelace/dist/themes/dark'; // Optional dark theme\n```\n\nFore more details about themes, please refer to [Theme Basics](/getting-started/themes#theme-basics).\n\n### Importing Required Scripts\n\nAfter importing the theme, you'll need to import the JavaScript files for Shoelace. Add the following code to `app/javascript/packs/application.js`.\n\n```js\nimport '../stylesheets/application.scss'\nimport { setBasePath, SlAlert, SlAnimation, SlButton, ... } from '@shoelace-style/shoelace'\n\n// ...\n\nconst rootUrl = document.currentScript.src.replace(/\\/packs.*$/, '')\n\n// Path to the assets folder (should be independent from the current script source path\n// to work correctly in different environments)\nsetBasePath(rootUrl + '/packs/js/')\n```\n\n### webpack Config\n\nNext we need to add Shoelace's assets to the final build output. To do this, modify `config/webpack/environment.js` to look like this.\n\n```js\nconst { environment } = require('@rails/webpacker');\n\n// Shoelace config\nconst path = require('path');\nconst CopyPlugin = require('copy-webpack-plugin');\n\n// Add shoelace assets to webpack's build process\nenvironment.plugins.append(\n  'CopyPlugin',\n  new CopyPlugin({\n    patterns: [\n      {\n        from: path.resolve(__dirname, '../../node_modules/@shoelace-style/shoelace/dist/assets'),\n        to: path.resolve(__dirname, '../../public/packs/js/assets')\n      }\n    ]\n  })\n);\n\nmodule.exports = environment;\n```\n\n### Adding Pack Tags\n\nThe final step is to add the corresponding `pack_tags` to the page. You should have the following `tags` in the `<head>` section of `app/views/layouts/application.html.erb`.\n\n```html\n<!doctype html>\n<html>\n  <head>\n    <!-- ... -->\n\n    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag\n    'application', 'data-turbolinks-track': 'reload' %>\n  </head>\n  <body>\n    <%= yield %>\n  </body>\n</html>\n```\n\nNow you can start using Shoelace components with Rails!\n\n## Additional Resources\n\n- There is a third-party [example repo](https://github.com/ParamagicDev/rails-shoelace-example), courtesy of [ParamagicDev](https://github.com/ParamagicDev) available to help you get started.\n- If you would like to avoid repeating this process, check out the associated [Railsbyte for Shoelace](https://railsbytes.com/templates/X8BsEb).\n"
  },
  {
    "path": "lint-staged.config.js",
    "content": "export default {\n  '*.{js,ts,json,html,xml,css,scss,sass,md}': 'cspell --no-must-find-files',\n  'src/**/*.{js,ts}': 'eslint --max-warnings 0 --fix',\n  '*': 'prettier --write --ignore-unknown'\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@shoelace-style/shoelace\",\n  \"description\": \"A forward-thinking library of web components.\",\n  \"version\": \"2.20.1\",\n  \"homepage\": \"https://github.com/shoelace-style/shoelace\",\n  \"author\": \"Cory LaViska\",\n  \"license\": \"MIT\",\n  \"customElements\": \"dist/custom-elements.json\",\n  \"web-types\": \"./dist/web-types.json\",\n  \"type\": \"module\",\n  \"types\": \"dist/shoelace.d.ts\",\n  \"jsdelivr\": \"./cdn/shoelace-autoloader.js\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/shoelace.d.ts\",\n      \"import\": \"./dist/shoelace.js\"\n    },\n    \"./dist/custom-elements.json\": \"./dist/custom-elements.json\",\n    \"./dist/shoelace.js\": \"./dist/shoelace.js\",\n    \"./dist/shoelace-autoloader.js\": \"./dist/shoelace-autoloader.js\",\n    \"./dist/themes\": \"./dist/themes\",\n    \"./dist/themes/*\": \"./dist/themes/*\",\n    \"./dist/components\": \"./dist/components\",\n    \"./dist/components/*\": \"./dist/components/*\",\n    \"./dist/utilities\": \"./dist/utilities\",\n    \"./dist/utilities/*\": \"./dist/utilities/*\",\n    \"./dist/react\": \"./dist/react/index.js\",\n    \"./dist/react/*\": \"./dist/react/*\",\n    \"./dist/translations\": \"./dist/translations\",\n    \"./dist/translations/*\": \"./dist/translations/*\"\n  },\n  \"files\": [\n    \"dist\",\n    \"cdn\"\n  ],\n  \"keywords\": [\n    \"web components\",\n    \"custom elements\",\n    \"components\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/shoelace-style/shoelace.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/shoelace-style/shoelace/issues\"\n  },\n  \"funding\": {\n    \"type\": \"individual\",\n    \"url\": \"https://github.com/sponsors/claviska\"\n  },\n  \"scripts\": {\n    \"start\": \"node scripts/build.js --serve\",\n    \"build\": \"node scripts/build.js\",\n    \"verify\": \"npm run prettier:check && npm run lint && npm run build && npm run test\",\n    \"prepare\": \"npx playwright install\",\n    \"prepublishOnly\": \"npm run verify\",\n    \"prettier\": \"prettier --write --log-level=warn .\",\n    \"prettier:check\": \"prettier --check --log-level=warn .\",\n    \"lint\": \"eslint src --max-warnings 0\",\n    \"lint:fix\": \"eslint src --max-warnings 0 --fix\",\n    \"create\": \"plop --plopfile scripts/plop/plopfile.js\",\n    \"test\": \"web-test-runner --group default\",\n    \"test:component\": \"web-test-runner -- --watch --group\",\n    \"test:watch\": \"web-test-runner --watch --group default\",\n    \"spellcheck\": \"cspell \\\"**/*.{js,ts,json,html,css,md}\\\" --no-progress\",\n    \"check-updates\": \"npx npm-check-updates --interactive --format group\"\n  },\n  \"engines\": {\n    \"node\": \">=14.17.0\"\n  },\n  \"dependencies\": {\n    \"@ctrl/tinycolor\": \"^4.1.0\",\n    \"@floating-ui/dom\": \"^1.6.12\",\n    \"@lit/react\": \"^1.0.6\",\n    \"@shoelace-style/animations\": \"^1.2.0\",\n    \"@shoelace-style/localize\": \"^3.2.1\",\n    \"composed-offset-position\": \"^0.0.6\",\n    \"lit\": \"^3.2.1\",\n    \"qr-creator\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"@11ty/eleventy\": \"^2.0.1\",\n    \"@custom-elements-manifest/analyzer\": \"^0.10.3\",\n    \"@open-wc/testing\": \"^4.0.0\",\n    \"@types/mocha\": \"^10.0.10\",\n    \"@types/react\": \"^18.3.13\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.7.5\",\n    \"@typescript-eslint/parser\": \"^6.7.5\",\n    \"@web/dev-server-esbuild\": \"^1.0.3\",\n    \"@web/test-runner\": \"^0.19.0\",\n    \"@web/test-runner-commands\": \"^0.9.0\",\n    \"@web/test-runner-playwright\": \"^0.11.0\",\n    \"bootstrap-icons\": \"^1.11.3\",\n    \"browser-sync\": \"^3.0.3\",\n    \"chalk\": \"^5.3.0\",\n    \"change-case\": \"^4.1.2\",\n    \"command-line-args\": \"^6.0.1\",\n    \"comment-parser\": \"^1.4.1\",\n    \"cspell\": \"^8.16.1\",\n    \"custom-element-jet-brains-integration\": \"^1.6.2\",\n    \"custom-element-vs-code-integration\": \"^1.4.1\",\n    \"custom-element-vuejs-integration\": \"^1.3.3\",\n    \"del\": \"^8.0.0\",\n    \"download\": \"^8.0.0\",\n    \"esbuild\": \"^0.24.0\",\n    \"esbuild-plugin-replace\": \"^1.4.0\",\n    \"eslint\": \"^8.51.0\",\n    \"eslint-plugin-chai-expect\": \"^3.1.0\",\n    \"eslint-plugin-chai-friendly\": \"^0.7.2\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-lit\": \"^1.15.0\",\n    \"eslint-plugin-lit-a11y\": \"^4.1.4\",\n    \"eslint-plugin-markdown\": \"^3.0.1\",\n    \"eslint-plugin-sort-imports-es6-autofix\": \"^0.6.0\",\n    \"eslint-plugin-wc\": \"^2.2.0\",\n    \"front-matter\": \"^4.0.2\",\n    \"get-port\": \"^7.1.0\",\n    \"globby\": \"^14.0.2\",\n    \"husky\": \"^9.1.7\",\n    \"jsdom\": \"^25.0.1\",\n    \"lint-staged\": \"^15.2.10\",\n    \"lunr\": \"^2.3.9\",\n    \"markdown-it-container\": \"^4.0.0\",\n    \"markdown-it-ins\": \"^4.0.0\",\n    \"markdown-it-kbd\": \"^2.2.2\",\n    \"markdown-it-mark\": \"^4.0.0\",\n    \"markdown-it-replace-it\": \"^1.0.0\",\n    \"npm-check-updates\": \"^17.1.11\",\n    \"ora\": \"^8.1.1\",\n    \"pascal-case\": \"^4.0.0\",\n    \"plop\": \"^4.0.1\",\n    \"prettier\": \"^3.4.2\",\n    \"prismjs\": \"^1.29.0\",\n    \"react\": \"^18.3.1\",\n    \"recursive-copy\": \"^2.0.14\",\n    \"sinon\": \"^19.0.2\",\n    \"smartquotes\": \"^2.3.2\",\n    \"source-map\": \"^0.7.4\",\n    \"strip-css-comments\": \"^5.0.0\",\n    \"tslib\": \"^2.8.1\",\n    \"typescript\": \"^5.7.2\",\n    \"user-agent-data-types\": \"^0.4.2\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,js}\": [\n      \"eslint --max-warnings 0 --cache --fix\",\n      \"prettier --write\"\n    ]\n  }\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "/** @type {import(\"prettier\").Config} */\nconst config = {\n  arrowParens: 'avoid',\n  bracketSpacing: true,\n  htmlWhitespaceSensitivity: 'css',\n  insertPragma: false,\n  bracketSameLine: false,\n  jsxSingleQuote: false,\n  printWidth: 120,\n  proseWrap: 'preserve',\n  quoteProps: 'as-needed',\n  requirePragma: false,\n  semi: true,\n  singleQuote: true,\n  tabWidth: 2,\n  trailingComma: 'none',\n  useTabs: false\n};\n\nexport default config;\n"
  },
  {
    "path": "scripts/build.js",
    "content": "import { deleteAsync } from 'del';\nimport { exec, spawn } from 'child_process';\nimport { globby } from 'globby';\nimport browserSync from 'browser-sync';\nimport chalk from 'chalk';\nimport commandLineArgs from 'command-line-args';\nimport copy from 'recursive-copy';\nimport esbuild from 'esbuild';\nimport fs from 'fs/promises';\nimport getPort, { portNumbers } from 'get-port';\nimport ora from 'ora';\nimport util from 'util';\nimport * as path from 'path';\nimport { readFileSync } from 'fs';\nimport { replace } from 'esbuild-plugin-replace';\n\nconst { serve } = commandLineArgs([{ name: 'serve', type: Boolean }]);\nconst outdir = 'dist';\nconst cdndir = 'cdn';\nconst sitedir = '_site';\nconst spinner = ora({ hideCursor: false }).start();\nconst execPromise = util.promisify(exec);\nlet childProcess;\nlet buildResults;\n\nconst bundleDirectories = [cdndir, outdir];\nlet packageData = JSON.parse(readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));\nconst shoelaceVersion = JSON.stringify(packageData.version.toString());\n\n//\n// Runs 11ty and builds the docs. The returned promise resolves after the initial publish has completed. The child\n// process and an array of strings containing any output are included in the resolved promise.\n//\nasync function buildTheDocs(watch = false) {\n  return new Promise(async (resolve, reject) => {\n    const afterSignal = '[eleventy.after]';\n\n    // Totally non-scientific way to handle errors. Perhaps its just better to resolve on stderr? :shrug:\n    const errorSignal = 'Original error stack trace:';\n    const args = ['@11ty/eleventy', '--quiet'];\n    const output = [];\n\n    if (watch) {\n      args.push('--watch');\n      args.push('--incremental');\n    }\n\n    const child = spawn('npx', args, {\n      stdio: 'pipe',\n      cwd: 'docs',\n      shell: true // for Windows\n    });\n\n    child.stdout.on('data', data => {\n      if (data.includes(afterSignal)) return; // don't log the signal\n      output.push(data.toString());\n    });\n\n    child.stderr.on('data', data => {\n      output.push(data.toString());\n    });\n\n    if (watch) {\n      // The process doesn't terminate in watch mode so, before resolving, we listen for a known signal in stdout that\n      // tells us when the first build completes.\n      child.stdout.on('data', data => {\n        if (data.includes(afterSignal)) {\n          resolve({ child, output });\n        }\n      });\n\n      child.stderr.on('data', data => {\n        if (data.includes(errorSignal)) {\n          // This closes the dev server, not sure if thats what we want?\n          reject(output);\n        }\n      });\n    } else {\n      child.on('close', () => {\n        resolve({ child, output });\n      });\n    }\n  });\n}\n\n//\n// Builds the source with esbuild.\n//\nasync function buildTheSource() {\n  const alwaysExternal = ['@lit/react', 'react'];\n\n  const cdnConfig = {\n    format: 'esm',\n    target: 'es2017',\n    entryPoints: [\n      //\n      // NOTE: Entry points must be mapped in package.json > exports, otherwise users won't be able to import them!\n      //\n      // The whole shebang\n      './src/shoelace.ts',\n      // The auto-loader\n      './src/shoelace-autoloader.ts',\n      // Components\n      ...(await globby('./src/components/**/!(*.(style|test)).ts')),\n      // Translations\n      ...(await globby('./src/translations/**/*.ts')),\n      // Public utilities\n      ...(await globby('./src/utilities/**/!(*.(style|test)).ts')),\n      // Theme stylesheets\n      ...(await globby('./src/themes/**/!(*.test).ts')),\n      // React wrappers\n      ...(await globby('./src/react/**/*.ts'))\n    ],\n    outdir: cdndir,\n    chunkNames: 'chunks/[name].[hash]',\n    define: {\n      // Floating UI requires this to be set\n      'process.env.NODE_ENV': '\"production\"'\n    },\n    bundle: true,\n    //\n    // We don't bundle certain dependencies in the unbundled build. This ensures we ship bare module specifiers,\n    // allowing end users to better optimize when using a bundler. (Only packages that ship ESM can be external.)\n    //\n    // We never bundle React or @lit/react though!\n    //\n    external: alwaysExternal,\n    splitting: true,\n    plugins: [\n      replace({\n        __SHOELACE_VERSION__: shoelaceVersion\n      })\n    ]\n  };\n\n  const npmConfig = {\n    ...cdnConfig,\n    external: undefined,\n    minify: false,\n    packages: 'external',\n    outdir\n  };\n\n  if (serve) {\n    // Use the context API to allow incremental dev builds\n    const contexts = await Promise.all([esbuild.context(cdnConfig), esbuild.context(npmConfig)]);\n    await Promise.all(contexts.map(context => context.rebuild()));\n    return contexts;\n  } else {\n    // Use the standard API for production builds\n    return await Promise.all([esbuild.build(cdnConfig), esbuild.build(npmConfig)]);\n  }\n}\n\n//\n// Called on SIGINT or SIGTERM to cleanup the build and child processes.\n//\nfunction handleCleanup() {\n  buildResults.forEach(result => result.dispose());\n\n  if (childProcess) {\n    childProcess.kill('SIGINT');\n  }\n\n  process.exit();\n}\n\n//\n// Helper function to draw a spinner while tasks run.\n//\nasync function nextTask(label, action) {\n  spinner.text = label;\n  spinner.start();\n\n  try {\n    await action();\n    spinner.stop();\n    console.log(`${chalk.green('✔')} ${label}`);\n  } catch (err) {\n    spinner.stop();\n    console.error(`${chalk.red('✘')} ${err}`);\n    if (err.stdout) console.error(chalk.red(err.stdout));\n    if (err.stderr) console.error(chalk.red(err.stderr));\n    process.exit(1);\n  }\n}\n\nawait nextTask('Cleaning up the previous build', async () => {\n  await Promise.all([deleteAsync(sitedir), ...bundleDirectories.map(dir => deleteAsync(dir))]);\n  await fs.mkdir(outdir, { recursive: true });\n});\n\nawait nextTask('Generating component metadata', () => {\n  return Promise.all(\n    bundleDirectories.map(dir => {\n      return execPromise(`node scripts/make-metadata.js --outdir \"${dir}\"`, { stdio: 'inherit' });\n    })\n  );\n});\n\nawait nextTask('Wrapping components for React', () => {\n  return execPromise(`node scripts/make-react.js --outdir \"${outdir}\"`, { stdio: 'inherit' });\n});\n\nawait nextTask('Generating themes', () => {\n  return execPromise(`node scripts/make-themes.js --outdir \"${outdir}\"`, { stdio: 'inherit' });\n});\n\nawait nextTask('Packaging up icons', () => {\n  return execPromise(`node scripts/make-icons.js --outdir \"${outdir}\"`, { stdio: 'inherit' });\n});\n\nawait nextTask('Running the TypeScript compiler', () => {\n  return execPromise(`tsc --project ./tsconfig.prod.json --outdir \"${outdir}\"`, { stdio: 'inherit' });\n});\n\n// Copy the above steps to the CDN directory directly so we don't need to twice the work for nothing.\nawait nextTask(`Themes, Icons, and TS Types to \"${cdndir}\"`, async () => {\n  await deleteAsync(cdndir);\n  await copy(outdir, cdndir);\n});\n\nawait nextTask('Building source files', async () => {\n  buildResults = await buildTheSource();\n});\n\n// Copy the CDN build to the docs (prod only; we use a virtual directory in dev)\nif (!serve) {\n  await nextTask(`Copying the build to \"${sitedir}\"`, async () => {\n    await deleteAsync(sitedir);\n\n    // We copy the CDN build because that has everything bundled. Yes this looks weird.\n    // But if we do \"/cdn\" it requires changes all the docs to do /cdn instead of /dist.\n    await copy(cdndir, path.join(sitedir, 'dist'));\n  });\n}\n\n// Launch the dev server\nif (serve) {\n  let result;\n\n  // Spin up Eleventy and Wait for the search index to appear before proceeding. The search index is generated during\n  // eleventy.after, so it appears after the docs are fully published. This is kinda hacky, but here we are.\n  // Kick off the Eleventy dev server with --watch and --incremental\n  await nextTask('Building docs', async () => {\n    result = await buildTheDocs(true);\n  });\n\n  const bs = browserSync.create();\n  const port = await getPort({ port: portNumbers(4000, 4999) });\n  const browserSyncConfig = {\n    startPath: '/',\n    port,\n    logLevel: 'silent',\n    logPrefix: '[shoelace]',\n    logFileChanges: true,\n    notify: false,\n    single: false,\n    ghostMode: false,\n    server: {\n      baseDir: sitedir,\n      routes: {\n        '/dist': './cdn'\n      }\n    }\n  };\n\n  // Launch browser sync\n  bs.init(browserSyncConfig, () => {\n    const url = `http://localhost:${port}`;\n    console.log(chalk.cyan(`\\n🥾 The dev server is available at ${url}`));\n\n    // Log deferred output\n    if (result.output.length > 0) {\n      console.log('\\n' + result.output.join('\\n'));\n    }\n\n    // Log output that comes later on\n    result.child.stdout.on('data', data => {\n      console.log(data.toString());\n    });\n  });\n\n  // Rebuild and reload when source files change\n  bs.watch('src/**/!(*.test).*').on('change', async filename => {\n    console.log('[build] File changed: ', filename);\n\n    try {\n      const isTheme = /^src\\/themes/.test(filename);\n      const isStylesheet = /(\\.css|\\.styles\\.ts)$/.test(filename);\n\n      // Rebuild the source\n      const rebuildResults = buildResults.map(result => result.rebuild());\n      await Promise.all(rebuildResults);\n\n      // Rebuild stylesheets when a theme file changes\n      if (isTheme) {\n        await Promise.all(\n          bundleDirectories.map(dir => {\n            execPromise(`node scripts/make-themes.js --outdir \"${dir}\"`, { stdio: 'inherit' });\n          })\n        );\n      }\n\n      // Rebuild metadata (but not when styles are changed)\n      if (!isStylesheet) {\n        await Promise.all(\n          bundleDirectories.map(dir => {\n            return execPromise(`node scripts/make-metadata.js --outdir \"${dir}\"`, { stdio: 'inherit' });\n          })\n        );\n      }\n\n      bs.reload();\n    } catch (err) {\n      console.error(chalk.red(err));\n    }\n  });\n\n  // Reload without rebuilding when the docs change\n  bs.watch([`${sitedir}/**/*.*`]).on('change', filename => {\n    bs.reload();\n  });\n}\n\n// Build for production\nif (!serve) {\n  let result;\n\n  await nextTask('Building the docs', async () => {\n    result = await buildTheDocs();\n  });\n\n  // Log deferred output\n  if (result.output.length > 0) {\n    console.log('\\n' + result.output.join('\\n'));\n  }\n}\n\n// Cleanup on exit\nprocess.on('SIGINT', handleCleanup);\nprocess.on('SIGTERM', handleCleanup);\n"
  },
  {
    "path": "scripts/make-icons.js",
    "content": "//\n// This script downloads and generates icons and icon metadata.\n//\nimport chalk from 'chalk';\nimport commandLineArgs from 'command-line-args';\nimport copy from 'recursive-copy';\nimport { deleteAsync } from 'del';\nimport download from 'download';\nimport fm from 'front-matter';\nimport fs from 'fs/promises';\nimport { globby } from 'globby';\nimport path from 'path';\n\nconst { outdir } = commandLineArgs({ name: 'outdir', type: String });\nconst iconDir = path.join(outdir, '/assets/icons');\n\nconst iconPackageData = JSON.parse(await fs.readFile('./node_modules/bootstrap-icons/package.json', 'utf8'));\n\nconst version = iconPackageData.version;\nconst srcPath = `./.cache/icons/icons-${version}`;\nconst url = `https://github.com/twbs/icons/archive/v${version}.zip`;\n\ntry {\n  await fs.stat(`${srcPath}/LICENSE`);\n} catch {\n  // Download the source from GitHub (since not everything is published to npm)\n  await download(url, './.cache/icons', { extract: true });\n}\n\n// Copy icons\nawait deleteAsync([iconDir]);\nawait fs.mkdir(iconDir, { recursive: true });\nawait Promise.all([\n  copy(`${srcPath}/icons`, iconDir),\n  copy(`${srcPath}/LICENSE`, path.join(iconDir, 'LICENSE')),\n  copy(`${srcPath}/bootstrap-icons.svg`, './docs/assets/images/sprite.svg', { overwrite: true })\n]);\n\n// Generate metadata\nconst files = await globby(`${srcPath}/docs/content/icons/**/*.md`);\nconst metadata = await Promise.all(\n  files.map(async file => {\n    const name = path.basename(file, path.extname(file));\n    const data = fm(await fs.readFile(file, 'utf8')).attributes;\n\n    return {\n      name,\n      title: data.title,\n      categories: data.categories,\n      tags: data.tags\n    };\n  })\n);\n\nawait fs.writeFile(path.join(iconDir, 'icons.json'), JSON.stringify(metadata, null, 2), 'utf8');\n"
  },
  {
    "path": "scripts/make-metadata.js",
    "content": "//\n// This script runs the Custom Elements Manifest analyzer to generate custom-elements.json\n//\n\nimport { execSync } from 'child_process';\nimport commandLineArgs from 'command-line-args';\n\nconst { outdir } = commandLineArgs({ name: 'outdir', type: String });\n\nexecSync(`cem analyze --litelement --outdir \"${outdir}\"`, { stdio: 'inherit' });\n"
  },
  {
    "path": "scripts/make-react.js",
    "content": "import commandLineArgs from 'command-line-args';\nimport fs from 'fs';\nimport path from 'path';\nimport { deleteSync } from 'del';\nimport prettier from 'prettier';\nimport { default as prettierConfig } from '../prettier.config.js';\nimport { getAllComponents } from './shared.js';\n\nconst { outdir } = commandLineArgs({ name: 'outdir', type: String });\n\nconst reactDir = path.join('./src/react');\n\n// Clear build directory\ndeleteSync(reactDir);\nfs.mkdirSync(reactDir, { recursive: true });\n\n// Fetch component metadata\nconst metadata = JSON.parse(fs.readFileSync(path.join(outdir, 'custom-elements.json'), 'utf8'));\nconst components = getAllComponents(metadata);\nconst index = [];\n\nfor await (const component of components) {\n  const tagWithoutPrefix = component.tagName.replace(/^sl-/, '');\n  const componentDir = path.join(reactDir, tagWithoutPrefix);\n  const componentFile = path.join(componentDir, 'index.ts');\n  const importPath = component.path.replace(/\\.js$/, '.component.js');\n  const eventImports = (component.events || [])\n    .map(event => `import type { ${event.eventName} } from '../../events/events.js';`)\n    .join('\\n');\n  const eventExports = (component.events || [])\n    .map(event => `export type { ${event.eventName} } from '../../events/events.js';`)\n    .join('\\n');\n  const eventNameImport = (component.events || []).length > 0 ? `import { type EventName } from '@lit/react';` : ``;\n  const events = (component.events || [])\n    .map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)\n    .join(',\\n');\n\n  fs.mkdirSync(componentDir, { recursive: true });\n\n  const jsDoc = component.jsDoc || '';\n\n  const source = await prettier.format(\n    `\n      import * as React from 'react';\n      import { createComponent } from '@lit/react';\n      import Component from '../../${importPath}';\n\n      ${eventNameImport}\n      ${eventImports}\n      ${eventExports}\n\n      const tagName = '${component.tagName}'\n      Component.define('${component.tagName}')\n\n      ${jsDoc}\n      const reactWrapper = createComponent({\n        tagName,\n        elementClass: Component,\n        react: React,\n        events: {\n          ${events}\n        },\n        displayName: \"${component.name}\"\n      })\n\n      export default reactWrapper\n    `,\n    Object.assign(prettierConfig, {\n      parser: 'babel-ts'\n    })\n  );\n\n  index.push(`export { default as ${component.name} } from './${tagWithoutPrefix}/index.js';`);\n\n  fs.writeFileSync(componentFile, source, 'utf8');\n}\n\n// Generate the index file\nfs.writeFileSync(path.join(reactDir, 'index.ts'), index.join('\\n'), 'utf8');\n"
  },
  {
    "path": "scripts/make-themes.js",
    "content": "//\n// This script bakes and copies themes, then generates a corresponding Lit stylesheet in dist/themes\n//\nimport chalk from 'chalk';\nimport commandLineArgs from 'command-line-args';\nimport fs from 'fs';\nimport { mkdirSync } from 'fs';\nimport { globbySync } from 'globby';\nimport path from 'path';\nimport prettier from 'prettier';\nimport stripComments from 'strip-css-comments';\n\nconst { outdir } = commandLineArgs({ name: 'outdir', type: String });\nconst files = globbySync('./src/themes/**/[!_]*.css');\nconst filesToEmbed = globbySync('./src/themes/**/_*.css');\nconst themesDir = path.join(outdir, 'themes');\nconst embeds = {};\n\nmkdirSync(themesDir, { recursive: true });\n\n// Gather an object containing the source of all files named \"_filename.css\" so we can embed them later\nfilesToEmbed.forEach(file => {\n  embeds[path.basename(file)] = fs.readFileSync(file, 'utf8');\n});\n\n// Loop through each theme file, copying the .css and generating a .js version for Lit users\nfiles.forEach(async file => {\n  let source = fs.readFileSync(file, 'utf8');\n\n  // If the source has \"/* _filename.css */\" in it, replace it with the embedded styles\n  Object.keys(embeds).forEach(key => {\n    source = source.replace(`/* ${key} */`, embeds[key]);\n  });\n\n  const css = await prettier.format(stripComments(source), {\n    parser: 'css'\n  });\n\n  let js = await prettier.format(\n    `\n    import { css } from 'lit';\n\n    export default css\\`\n      ${css}\n    \\`;\n  `,\n    { parser: 'babel-ts' }\n  );\n\n  let dTs = await prettier.format(\n    `\n    declare const _default: import(\"lit\").CSSResult;\n    export default _default;\n  `,\n    { parser: 'babel-ts' }\n  );\n\n  const cssFile = path.join(themesDir, path.basename(file));\n  const jsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.js'));\n  const dTsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.d.ts'));\n\n  fs.writeFileSync(cssFile, css, 'utf8');\n  fs.writeFileSync(jsFile, js, 'utf8');\n  fs.writeFileSync(dTsFile, dTs, 'utf8');\n});\n"
  },
  {
    "path": "scripts/plop/plopfile.js",
    "content": "export default function (plop) {\n  plop.setHelper('tagWithoutPrefix', tag => tag.replace(/^sl-/, ''));\n\n  plop.setHelper('tagToTitle', tag => {\n    const withoutPrefix = plop.getHelper('tagWithoutPrefix');\n    const titleCase = plop.getHelper('titleCase');\n    return titleCase(withoutPrefix(tag).replace(/-/g, ' '));\n  });\n\n  plop.setGenerator('component', {\n    description: 'Generate a new component',\n    prompts: [\n      {\n        type: 'input',\n        name: 'tag',\n        message: 'Tag name? (e.g. sl-button)',\n        validate: value => {\n          // Start with sl- and include only a-z + dashes\n          if (!/^sl-[a-z-+]+/.test(value)) {\n            return false;\n          }\n\n          // No double dashes or ending dash\n          if (value.includes('--') || value.endsWith('-')) {\n            return false;\n          }\n\n          return true;\n        }\n      }\n    ],\n    actions: [\n      {\n        type: 'add',\n        path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.ts',\n        templateFile: 'templates/component/define.hbs'\n      },\n      {\n        type: 'add',\n        path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.component.ts',\n        templateFile: 'templates/component/component.hbs'\n      },\n      {\n        type: 'add',\n        path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.styles.ts',\n        templateFile: 'templates/component/styles.hbs'\n      },\n      {\n        type: 'add',\n        path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.test.ts',\n        templateFile: 'templates/component/tests.hbs'\n      },\n      {\n        type: 'add',\n        path: '../../docs/pages/components/{{ tagWithoutPrefix tag }}.md',\n        templateFile: 'templates/component/docs.hbs'\n      },\n      {\n        type: 'modify',\n        path: '../../src/shoelace.ts',\n        pattern: /\\/\\* plop:component \\*\\//,\n        template: `export { default as {{ properCase tag }} } from './components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.js';\\n/* plop:component */`\n      }\n    ]\n  });\n}\n"
  },
  {
    "path": "scripts/plop/templates/component/component.hbs",
    "content": "import { property } from 'lit/decorators.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './{{ tagWithoutPrefix tag }}.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Short summary of the component's intended use.\n * @documentation https://shoelace.style/components/{{ tagWithoutPrefix tag }}\n * @status experimental\n * @since 2.0\n *\n * @dependency sl-example\n *\n * @event sl-event-name - Emitted as an example.\n *\n * @slot - The default slot.\n * @slot example - An example slot.\n *\n * @csspart base - The component's base wrapper.\n *\n * @cssproperty --example - An example CSS custom property.\n */\nexport default class {{ properCase tag }} extends ShoelaceElement {\n    static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly localize = new LocalizeController(this);\n\n  /** An example attribute. */\n  @property() attr = 'example';\n\n  @watch('example')\n  handleExampleChange() {\n    // do something\n  }\n\n  render() {\n    return html` <slot></slot> `;\n  }\n}\n"
  },
  {
    "path": "scripts/plop/templates/component/define.hbs",
    "content": "import {{ properCase tag }} from './{{ tagWithoutPrefix tag }}.component.js';\n\nexport * from './{{ tagWithoutPrefix tag }}.component.js';\nexport default {{ properCase tag }};\n\n{{ properCase tag }}.define('{{ tag }}');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    '{{ tag }}': {{ properCase tag }};\n  }\n}\n"
  },
  {
    "path": "scripts/plop/templates/component/docs.hbs",
    "content": "---\nmeta:\n  title: {{ tagToTitle tag }}\n  description:\nlayout: component\n---\n\n```html:preview\n<{{ tag }}></{{ tag }}>\n```\n\n## Examples\n\n### First Example\n\nTODO\n\n### Second Example\n\nTODO\n\n[component-metadata:{{ tag }}]\n"
  },
  {
    "path": "scripts/plop/templates/component/styles.hbs",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n`;\n"
  },
  {
    "path": "scripts/plop/templates/component/tests.hbs",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\n\ndescribe('<{{ tag }}>', () => {\n  it('should render a component', async () => {\n    const el = await fixture(html` <{{ tag }}></{{ tag }}> `);\n\n    expect(el).to.exist;\n  });\n});\n"
  },
  {
    "path": "scripts/shared.js",
    "content": "/** Gets an array of components from a CEM object. */\nexport function getAllComponents(metadata) {\n  const allComponents = [];\n\n  metadata.modules.map(module => {\n    module.declarations?.map(declaration => {\n      if (declaration.customElement) {\n        const component = declaration;\n        const path = module.path;\n\n        if (component) {\n          allComponents.push(Object.assign(component, { path }));\n        }\n      }\n    });\n  });\n\n  return allComponents;\n}\n"
  },
  {
    "path": "src/components/alert/alert.component.ts",
    "content": "import { animateTo, stopAnimations } from '../../internal/animate.js';\nimport { blurActiveElement } from '../../internal/closeActiveElement.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport styles from './alert.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Alerts are used to display important messages inline or as toast notifications.\n * @documentation https://shoelace.style/components/alert\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - The alert's main content.\n * @slot icon - An icon to show in the alert. Works best with `<sl-icon>`.\n *\n * @event sl-show - Emitted when the alert opens.\n * @event sl-after-show - Emitted after the alert opens and all animations are complete.\n * @event sl-hide - Emitted when the alert closes.\n * @event sl-after-hide - Emitted after the alert closes and all animations are complete.\n *\n * @csspart base - The component's base wrapper.\n * @csspart icon - The container that wraps the optional icon.\n * @csspart message - The container that wraps the alert's main content.\n * @csspart close-button - The close button, an `<sl-icon-button>`.\n * @csspart close-button__base - The close button's exported `base` part.\n *\n * @animation alert.show - The animation to use when showing the alert.\n * @animation alert.hide - The animation to use when hiding the alert.\n */\nexport default class SlAlert extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon-button': SlIconButton };\n\n  private autoHideTimeout: number;\n  private remainingTimeInterval: number;\n  private countdownAnimation?: Animation;\n  private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');\n  private readonly localize = new LocalizeController(this);\n\n  private static currentToastStack: HTMLDivElement;\n\n  private static get toastStack() {\n    if (!this.currentToastStack) {\n      this.currentToastStack = Object.assign(document.createElement('div'), {\n        className: 'sl-toast-stack'\n      });\n    }\n    return this.currentToastStack;\n  }\n\n  @query('[part~=\"base\"]') base: HTMLElement;\n\n  @query('.alert__countdown-elapsed') countdownElement: HTMLElement;\n\n  /**\n   * Indicates whether or not the alert is open. You can toggle this attribute to show and hide the alert, or you can\n   * use the `show()` and `hide()` methods and this attribute will reflect the alert's open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /** Enables a close button that allows the user to dismiss the alert. */\n  @property({ type: Boolean, reflect: true }) closable = false;\n\n  /** The alert's theme variant. */\n  @property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';\n\n  /**\n   * The length of time, in milliseconds, the alert will show before closing itself. If the user interacts with\n   * the alert before it closes (e.g. moves the mouse over it), the timer will restart. Defaults to `Infinity`, meaning\n   * the alert will not close on its own.\n   */\n  @property({ type: Number }) duration = Infinity;\n\n  /**\n   * Enables a countdown that indicates the remaining time the alert will be displayed.\n   * Typically used to indicate the remaining time before a whole app refresh.\n   */\n  @property({ type: String, reflect: true }) countdown?: 'rtl' | 'ltr';\n\n  @state() private remainingTime = this.duration;\n\n  firstUpdated() {\n    this.base.hidden = !this.open;\n  }\n\n  private restartAutoHide() {\n    this.handleCountdownChange();\n    clearTimeout(this.autoHideTimeout);\n    clearInterval(this.remainingTimeInterval);\n    if (this.open && this.duration < Infinity) {\n      this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);\n      this.remainingTime = this.duration;\n      this.remainingTimeInterval = window.setInterval(() => {\n        this.remainingTime -= 100;\n      }, 100);\n    }\n  }\n\n  private pauseAutoHide() {\n    this.countdownAnimation?.pause();\n    clearTimeout(this.autoHideTimeout);\n    clearInterval(this.remainingTimeInterval);\n  }\n\n  private resumeAutoHide() {\n    if (this.duration < Infinity) {\n      this.autoHideTimeout = window.setTimeout(() => this.hide(), this.remainingTime);\n      this.remainingTimeInterval = window.setInterval(() => {\n        this.remainingTime -= 100;\n      }, 100);\n      this.countdownAnimation?.play();\n    }\n  }\n\n  private handleCountdownChange() {\n    if (this.open && this.duration < Infinity && this.countdown) {\n      const { countdownElement } = this;\n      const start = '100%';\n      const end = '0';\n      this.countdownAnimation = countdownElement.animate([{ width: start }, { width: end }], {\n        duration: this.duration,\n        easing: 'linear'\n      });\n    }\n  }\n\n  private handleCloseClick() {\n    this.hide();\n  }\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open) {\n      // Show\n      this.emit('sl-show');\n\n      if (this.duration < Infinity) {\n        this.restartAutoHide();\n      }\n\n      await stopAnimations(this.base);\n      this.base.hidden = false;\n      const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });\n      await animateTo(this.base, keyframes, options);\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      blurActiveElement(this);\n      this.emit('sl-hide');\n\n      clearTimeout(this.autoHideTimeout);\n      clearInterval(this.remainingTimeInterval);\n\n      await stopAnimations(this.base);\n      const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });\n      await animateTo(this.base, keyframes, options);\n      this.base.hidden = true;\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  @watch('duration')\n  handleDurationChange() {\n    this.restartAutoHide();\n  }\n\n  /** Shows the alert. */\n  async show() {\n    if (this.open) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the alert */\n  async hide() {\n    if (!this.open) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  /**\n   * Displays the alert as a toast notification. This will move the alert out of its position in the DOM and, when\n   * dismissed, it will be removed from the DOM completely. By storing a reference to the alert, you can reuse it by\n   * calling this method again. The returned promise will resolve after the alert is hidden.\n   */\n  async toast() {\n    return new Promise<void>(resolve => {\n      this.handleCountdownChange();\n      if (SlAlert.toastStack.parentElement === null) {\n        document.body.append(SlAlert.toastStack);\n      }\n\n      SlAlert.toastStack.appendChild(this);\n\n      // Wait for the toast stack to render\n      requestAnimationFrame(() => {\n        // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- force a reflow for the initial transition\n        this.clientWidth;\n        this.show();\n      });\n\n      this.addEventListener(\n        'sl-after-hide',\n        () => {\n          SlAlert.toastStack.removeChild(this);\n          resolve();\n\n          // Remove the toast stack from the DOM when there are no more alerts\n          if (SlAlert.toastStack.querySelector('sl-alert') === null) {\n            SlAlert.toastStack.remove();\n          }\n        },\n        { once: true }\n      );\n    });\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          alert: true,\n          'alert--open': this.open,\n          'alert--closable': this.closable,\n          'alert--has-countdown': !!this.countdown,\n          'alert--has-icon': this.hasSlotController.test('icon'),\n          'alert--primary': this.variant === 'primary',\n          'alert--success': this.variant === 'success',\n          'alert--neutral': this.variant === 'neutral',\n          'alert--warning': this.variant === 'warning',\n          'alert--danger': this.variant === 'danger'\n        })}\n        role=\"alert\"\n        aria-hidden=${this.open ? 'false' : 'true'}\n        @mouseenter=${this.pauseAutoHide}\n        @mouseleave=${this.resumeAutoHide}\n      >\n        <div part=\"icon\" class=\"alert__icon\">\n          <slot name=\"icon\"></slot>\n        </div>\n\n        <div part=\"message\" class=\"alert__message\" aria-live=\"polite\">\n          <slot></slot>\n        </div>\n\n        ${this.closable\n          ? html`\n              <sl-icon-button\n                part=\"close-button\"\n                exportparts=\"base:close-button__base\"\n                class=\"alert__close-button\"\n                name=\"x-lg\"\n                library=\"system\"\n                label=${this.localize.term('close')}\n                @click=${this.handleCloseClick}\n              ></sl-icon-button>\n            `\n          : ''}\n\n        <div role=\"timer\" class=\"alert__timer\">${this.remainingTime}</div>\n\n        ${this.countdown\n          ? html`\n              <div\n                class=${classMap({\n                  alert__countdown: true,\n                  'alert__countdown--ltr': this.countdown === 'ltr'\n                })}\n              >\n                <div class=\"alert__countdown-elapsed\"></div>\n              </div>\n            `\n          : ''}\n      </div>\n    `;\n  }\n}\n\nsetDefaultAnimation('alert.show', {\n  keyframes: [\n    { opacity: 0, scale: 0.8 },\n    { opacity: 1, scale: 1 }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('alert.hide', {\n  keyframes: [\n    { opacity: 1, scale: 1 },\n    { opacity: 0, scale: 0.8 }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n"
  },
  {
    "path": "src/components/alert/alert.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: contents;\n\n    /* For better DX, we'll reset the margin here so the base part can inherit it */\n    margin: 0;\n  }\n\n  .alert {\n    position: relative;\n    display: flex;\n    align-items: stretch;\n    background-color: var(--sl-panel-background-color);\n    border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);\n    border-top-width: calc(var(--sl-panel-border-width) * 3);\n    border-radius: var(--sl-border-radius-medium);\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-small);\n    font-weight: var(--sl-font-weight-normal);\n    line-height: 1.6;\n    color: var(--sl-color-neutral-700);\n    margin: inherit;\n    overflow: hidden;\n  }\n\n  .alert:not(.alert--has-icon) .alert__icon,\n  .alert:not(.alert--closable) .alert__close-button {\n    display: none;\n  }\n\n  .alert__icon {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    font-size: var(--sl-font-size-large);\n    padding-inline-start: var(--sl-spacing-large);\n  }\n\n  .alert--has-countdown {\n    border-bottom: none;\n  }\n\n  .alert--primary {\n    border-top-color: var(--sl-color-primary-600);\n  }\n\n  .alert--primary .alert__icon {\n    color: var(--sl-color-primary-600);\n  }\n\n  .alert--success {\n    border-top-color: var(--sl-color-success-600);\n  }\n\n  .alert--success .alert__icon {\n    color: var(--sl-color-success-600);\n  }\n\n  .alert--neutral {\n    border-top-color: var(--sl-color-neutral-600);\n  }\n\n  .alert--neutral .alert__icon {\n    color: var(--sl-color-neutral-600);\n  }\n\n  .alert--warning {\n    border-top-color: var(--sl-color-warning-600);\n  }\n\n  .alert--warning .alert__icon {\n    color: var(--sl-color-warning-600);\n  }\n\n  .alert--danger {\n    border-top-color: var(--sl-color-danger-600);\n  }\n\n  .alert--danger .alert__icon {\n    color: var(--sl-color-danger-600);\n  }\n\n  .alert__message {\n    flex: 1 1 auto;\n    display: block;\n    padding: var(--sl-spacing-large);\n    overflow: hidden;\n  }\n\n  .alert__close-button {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    font-size: var(--sl-font-size-medium);\n    margin-inline-end: var(--sl-spacing-medium);\n    align-self: center;\n  }\n\n  .alert__countdown {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: calc(var(--sl-panel-border-width) * 3);\n    background-color: var(--sl-panel-border-color);\n    display: flex;\n  }\n\n  .alert__countdown--ltr {\n    justify-content: flex-end;\n  }\n\n  .alert__countdown .alert__countdown-elapsed {\n    height: 100%;\n    width: 0;\n  }\n\n  .alert--primary .alert__countdown-elapsed {\n    background-color: var(--sl-color-primary-600);\n  }\n\n  .alert--success .alert__countdown-elapsed {\n    background-color: var(--sl-color-success-600);\n  }\n\n  .alert--neutral .alert__countdown-elapsed {\n    background-color: var(--sl-color-neutral-600);\n  }\n\n  .alert--warning .alert__countdown-elapsed {\n    background-color: var(--sl-color-warning-600);\n  }\n\n  .alert--danger .alert__countdown-elapsed {\n    background-color: var(--sl-color-danger-600);\n  }\n\n  .alert__timer {\n    display: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/alert/alert.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport { clickOnElement, moveMouseOnElement } from '../../internal/test.js';\nimport { queryByTestId } from '../../internal/test/data-testid-helpers.js';\nimport { resetMouse } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlAlert from './alert.js';\nimport type SlIconButton from '../icon-button/icon-button.js';\n\nconst getAlertContainer = (alert: SlAlert): HTMLElement => {\n  return alert.shadowRoot!.querySelector<HTMLElement>('[part=\"base\"]')!;\n};\n\nconst expectAlertToBeVisible = (alert: SlAlert): void => {\n  const alertContainer = getAlertContainer(alert);\n  const style = window.getComputedStyle(alertContainer);\n  expect(style.display).not.to.equal('none');\n  expect(style.visibility).not.to.equal('hidden');\n  expect(style.visibility).not.to.equal('collapse');\n};\n\nconst expectAlertToBeInvisible = (alert: SlAlert): void => {\n  const alertContainer = getAlertContainer(alert);\n  const style = window.getComputedStyle(alertContainer);\n  expect(style.display, 'alert should be invisible').to.equal('none');\n};\n\nconst expectHideAndAfterHideToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {\n  const hidePromise = oneEvent(alert, 'sl-hide');\n  const afterHidePromise = oneEvent(alert, 'sl-after-hide');\n  let afterHideHappened = false;\n  oneEvent(alert, 'sl-after-hide').then(() => (afterHideHappened = true));\n\n  action();\n\n  await hidePromise;\n  expect(afterHideHappened).to.be.false;\n\n  await afterHidePromise;\n  expectAlertToBeInvisible(alert);\n};\n\nconst expectShowAndAfterShowToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {\n  const showPromise = oneEvent(alert, 'sl-show');\n  const afterShowPromise = oneEvent(alert, 'sl-after-show');\n  let afterShowHappened = false;\n  oneEvent(alert, 'sl-after-show').then(() => (afterShowHappened = true));\n\n  action();\n\n  await showPromise;\n  expect(afterShowHappened).to.be.false;\n\n  await afterShowPromise;\n  expectAlertToBeVisible(alert);\n};\n\nconst getCloseButton = (alert: SlAlert): SlIconButton | null | undefined =>\n  alert.shadowRoot?.querySelector<SlIconButton>('[part=\"close-button\"]');\n\ndescribe('<sl-alert>', () => {\n  let clock: sinon.SinonFakeTimers | null = null;\n\n  afterEach(async () => {\n    clock?.restore();\n    await resetMouse();\n  });\n\n  it('renders', async () => {\n    const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);\n\n    expectAlertToBeVisible(alert);\n  });\n\n  it('is accessible', async () => {\n    const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);\n\n    await expect(alert).to.be.accessible();\n  });\n\n  describe('alert visibility', () => {\n    it('should be visible with the open attribute', async () => {\n      const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);\n\n      expectAlertToBeVisible(alert);\n    });\n\n    it('should not be visible without the open attribute', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);\n\n      expectAlertToBeInvisible(alert);\n    });\n\n    it('should emit sl-show and sl-after-show when calling show()', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);\n\n      expectAlertToBeInvisible(alert);\n\n      await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.show());\n    });\n\n    it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert>`);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => alert.hide());\n    });\n\n    it('should emit sl-show and sl-after-show when setting open = true', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);\n\n      await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => {\n        alert.open = true;\n      });\n    });\n\n    it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {\n        alert.open = false;\n      });\n    });\n  });\n\n  describe('close button', () => {\n    it('shows a close button if the alert has the closable attribute', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);\n      const closeButton = getCloseButton(alert);\n\n      expect(closeButton).to.be.visible;\n    });\n\n    it('clicking the close button closes the alert', async () => {\n      const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);\n      const closeButton = getCloseButton(alert);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {\n        clickOnElement(closeButton!);\n      });\n    });\n\n    it('clicking above close button does not close the alert', async () => {\n      const wrapper = await fixture<HTMLDivElement>(\n        html`<div class=\"wrapper\" style=\"padding: 24px; background-color:red;\">\n          <sl-alert open closable>I am an alert</sl-alert>\n        </div>`\n      );\n      const alert = wrapper.querySelector('sl-alert')!;\n\n      const clickTargetPromise = new Promise<HTMLElement>(resolve => {\n        const clickHandler = sinon.spy((event: MouseEvent) => {\n          resolve(event.target as HTMLElement);\n        });\n        alert.shadowRoot!.addEventListener('click', clickHandler);\n        wrapper.addEventListener('click', clickHandler);\n      });\n\n      const closeButton = getCloseButton(alert);\n      await clickOnElement(closeButton!, 'top', 0, -4);\n      const clickTarget = await clickTargetPromise;\n      await expect(clickTarget.tagName.toLowerCase()).to.not.be.equal('sl-icon-button');\n      expect(clickTarget.classList.contains('alert')).to.be.true;\n      expect(clickTarget.classList.contains('wrapper'), 'The click should happen in the alert and not outside of it').to\n        .be.false;\n    });\n\n    it('clicking under close button does not close the alert', async () => {\n      const wrapper = await fixture<HTMLDivElement>(\n        html`<div class=\"wrapper\" style=\"padding: 24px; background-color:red;\">\n          <sl-alert open closable>I am an alert</sl-alert>\n        </div>`\n      );\n      const alert = wrapper.querySelector('sl-alert')!;\n\n      const clickTargetPromise = new Promise<HTMLElement>(resolve => {\n        const clickHandler = sinon.spy((event: MouseEvent) => {\n          resolve(event.target as HTMLElement);\n        });\n        alert.shadowRoot!.addEventListener('click', clickHandler);\n        wrapper.addEventListener('click', clickHandler);\n      });\n\n      const closeButton = getCloseButton(alert);\n      await clickOnElement(closeButton!, 'bottom', 0, 4);\n      const clickTarget = await clickTargetPromise;\n\n      await expect(clickTarget.tagName.toLowerCase()).to.not.be.equal('sl-icon-button');\n      expect(clickTarget.classList.contains('alert')).to.be.true;\n      expect(clickTarget.classList.contains('wrapper'), 'The click should happen in the alert and not outside of it').to\n        .be.false;\n    });\n\n    it('clicking on the right side of the close button does not close the alert', async () => {\n      const wrapper = await fixture<HTMLDivElement>(\n        html`<div class=\"wrapper\" style=\"padding: 24px; background-color:red;\">\n          <sl-alert open closable>I am an alert</sl-alert>\n        </div>`\n      );\n      const alert = wrapper.querySelector('sl-alert')!;\n\n      const clickTargetPromise = new Promise<HTMLElement>(resolve => {\n        const clickHandler = sinon.spy((event: MouseEvent) => {\n          resolve(event.target as HTMLElement);\n        });\n        alert.shadowRoot!.addEventListener('click', clickHandler);\n        wrapper.addEventListener('click', clickHandler);\n      });\n\n      const closeButton = getCloseButton(alert);\n      await clickOnElement(closeButton!, 'right', 4, 0);\n      const clickTarget = await clickTargetPromise;\n\n      await expect(clickTarget.tagName.toLowerCase()).to.not.be.equal('sl-icon-button');\n      expect(clickTarget.classList.contains('alert')).to.be.true;\n      expect(clickTarget.classList.contains('wrapper'), 'The click should happen in the alert and not outside of it').to\n        .be.false;\n    });\n  });\n\n  describe('toast', () => {\n    const getToastStack = (): HTMLDivElement | null => document.querySelector<HTMLDivElement>('.sl-toast-stack');\n\n    const closeRemainingAlerts = async (): Promise<void> => {\n      const toastStack = getToastStack();\n      if (toastStack?.children) {\n        for (const element of toastStack.children) {\n          await (element as SlAlert).hide();\n        }\n      }\n    };\n\n    beforeEach(async () => {\n      await closeRemainingAlerts();\n    });\n\n    it('can be rendered as a toast', async () => {\n      const alert = await fixture<SlAlert>(html`<sl-alert>I am an alert</sl-alert>`);\n\n      expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.toast());\n      const toastStack = getToastStack();\n      expect(toastStack).to.be.visible;\n      expect(toastStack?.firstChild).to.be.equal(alert);\n    });\n\n    it('resolves only after being closed', async () => {\n      const alert = await fixture<SlAlert>(html`<sl-alert closable>I am an alert</sl-alert>`);\n\n      const afterShowEvent = oneEvent(alert, 'sl-after-show');\n      let toastPromiseResolved = false;\n      alert.toast().then(() => (toastPromiseResolved = true));\n\n      await afterShowEvent;\n      expect(toastPromiseResolved).to.be.false;\n\n      const closePromise = oneEvent(alert, 'sl-after-hide');\n      const closeButton = getCloseButton(alert);\n      clickOnElement(closeButton!);\n\n      await closePromise;\n      await aTimeout(0);\n\n      expect(toastPromiseResolved).to.be.true;\n    });\n\n    const expectToastStack = () => {\n      const toastStack = getToastStack();\n      expect(toastStack).not.to.be.null;\n    };\n\n    const expectNoToastStack = () => {\n      const toastStack = getToastStack();\n      expect(toastStack).to.be.null;\n    };\n\n    const openToast = async (alert: SlAlert): Promise<void> => {\n      const openPromise = oneEvent(alert, 'sl-after-show');\n      alert.toast();\n      await openPromise;\n    };\n\n    const closeToast = async (alert: SlAlert): Promise<void> => {\n      const closePromise = oneEvent(alert, 'sl-after-hide');\n      const closeButton = getCloseButton(alert);\n      await clickOnElement(closeButton!);\n      await closePromise;\n      await aTimeout(0);\n    };\n\n    it('deletes the toast stack after the last alert is done', async () => {\n      const container = await fixture<HTMLElement>(\n        html`<div>\n          <sl-alert data-testid=\"alert1\" closable>alert 1</sl-alert>\n          <sl-alert data-testid=\"alert2\" closable>alert 2</sl-alert>\n        </div>`\n      );\n\n      const alert1 = queryByTestId<SlAlert>(container, 'alert1');\n      const alert2 = queryByTestId<SlAlert>(container, 'alert2');\n\n      await openToast(alert1!);\n\n      expectToastStack();\n\n      await openToast(alert2!);\n\n      expectToastStack();\n\n      await closeToast(alert1!);\n\n      expectToastStack();\n\n      await closeToast(alert2!);\n\n      expectNoToastStack();\n    });\n  });\n\n  describe('timer controlled closing', () => {\n    it('closes after a predefined amount of time', async () => {\n      clock = sinon.useFakeTimers();\n      const alert = await fixture<SlAlert>(html` <sl-alert open duration=\"3000\">I am an alert</sl-alert>`);\n\n      expectAlertToBeVisible(alert);\n\n      clock.tick(2999);\n\n      expectAlertToBeVisible(alert);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {\n        clock?.tick(1);\n      });\n    });\n\n    it('resets the closing timer after mouse-over', async () => {\n      clock = sinon.useFakeTimers();\n      const alert = await fixture<SlAlert>(html` <sl-alert open duration=\"3000\">I am an alert</sl-alert>`);\n\n      expectAlertToBeVisible(alert);\n\n      clock.tick(1000);\n\n      await moveMouseOnElement(alert);\n\n      clock.tick(2999);\n\n      expectAlertToBeVisible(alert);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {\n        clock?.tick(1);\n      });\n    });\n\n    it('resets the closing timer after opening', async () => {\n      clock = sinon.useFakeTimers();\n      const alert = await fixture<SlAlert>(html` <sl-alert duration=\"3000\">I am an alert</sl-alert>`);\n\n      expectAlertToBeInvisible(alert);\n\n      clock.tick(1000);\n\n      const afterShowPromise = oneEvent(alert, 'sl-after-show');\n      alert.show();\n      await afterShowPromise;\n\n      clock.tick(2999);\n\n      await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {\n        clock?.tick(1);\n      });\n    });\n  });\n\n  describe('alert variants', () => {\n    const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];\n\n    variants.forEach(variant => {\n      it(`adapts to the variant: ${variant}`, async () => {\n        const alert = await fixture<SlAlert>(html`<sl-alert variant=\"${variant}\" open>I am an alert</sl-alert>`);\n\n        const alertContainer = getAlertContainer(alert);\n        expect(alertContainer).to.have.class(`alert--${variant}`);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/alert/alert.ts",
    "content": "import SlAlert from './alert.component.js';\n\nexport * from './alert.component.js';\nexport default SlAlert;\n\nSlAlert.define('sl-alert');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-alert': SlAlert;\n  }\n}\n"
  },
  {
    "path": "src/components/animated-image/animated-image.component.ts",
    "content": "import { html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './animated-image.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction.\n * @documentation https://shoelace.style/components/animated-image\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @event sl-load - Emitted when the image loads successfully.\n * @event sl-error - Emitted when the image fails to load.\n *\n * @slot play-icon - Optional play icon to use instead of the default. Works best with `<sl-icon>`.\n * @slot pause-icon - Optional pause icon to use instead of the default. Works best with `<sl-icon>`.\n *\n * @part control-box - The container that surrounds the pause/play icons and provides their background.\n *\n * @cssproperty --control-box-size - The size of the icon box.\n * @cssproperty --icon-size - The size of the play/pause icons.\n */\nexport default class SlAnimatedImage extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  @query('.animated-image__animated') animatedImage: HTMLImageElement;\n\n  @state() frozenFrame: string;\n  @state() isLoaded = false;\n\n  /** The path to the image to load. */\n  @property() src: string;\n\n  /** A description of the image used by assistive devices. */\n  @property() alt: string;\n\n  /** Plays the animation. When this attribute is remove, the animation will pause. */\n  @property({ type: Boolean, reflect: true }) play: boolean;\n\n  private handleClick() {\n    this.play = !this.play;\n  }\n\n  private handleLoad() {\n    const canvas = document.createElement('canvas');\n    const { width, height } = this.animatedImage;\n    canvas.width = width;\n    canvas.height = height;\n    canvas.getContext('2d')!.drawImage(this.animatedImage, 0, 0, width, height);\n    this.frozenFrame = canvas.toDataURL('image/gif');\n\n    if (!this.isLoaded) {\n      this.emit('sl-load');\n      this.isLoaded = true;\n    }\n  }\n\n  private handleError() {\n    this.emit('sl-error');\n  }\n\n  @watch('play', { waitUntilFirstUpdate: true })\n  handlePlayChange() {\n    // When the animation starts playing, reset the src so it plays from the beginning. Since the src is cached, this\n    // won't trigger another request.\n    if (this.play) {\n      this.animatedImage.src = '';\n      this.animatedImage.src = this.src;\n    }\n  }\n\n  @watch('src')\n  handleSrcChange() {\n    this.isLoaded = false;\n  }\n\n  render() {\n    return html`\n      <div class=\"animated-image\">\n        <img\n          class=\"animated-image__animated\"\n          src=${this.src}\n          alt=${this.alt}\n          crossorigin=\"anonymous\"\n          aria-hidden=${this.play ? 'false' : 'true'}\n          @click=${this.handleClick}\n          @load=${this.handleLoad}\n          @error=${this.handleError}\n        />\n\n        ${this.isLoaded\n          ? html`\n              <img\n                class=\"animated-image__frozen\"\n                src=${this.frozenFrame}\n                alt=${this.alt}\n                aria-hidden=${this.play ? 'true' : 'false'}\n                @click=${this.handleClick}\n              />\n\n              <div part=\"control-box\" class=\"animated-image__control-box\">\n                <slot name=\"play-icon\"><sl-icon name=\"play-fill\" library=\"system\"></sl-icon></slot>\n                <slot name=\"pause-icon\"><sl-icon name=\"pause-fill\" library=\"system\"></sl-icon></slot>\n              </div>\n            `\n          : ''}\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/animated-image/animated-image.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --control-box-size: 3rem;\n    --icon-size: calc(var(--control-box-size) * 0.625);\n\n    display: inline-flex;\n    position: relative;\n    cursor: pointer;\n  }\n\n  img {\n    display: block;\n    width: 100%;\n    height: 100%;\n  }\n\n  img[aria-hidden='true'] {\n    display: none;\n  }\n\n  .animated-image__control-box {\n    display: flex;\n    position: absolute;\n    align-items: center;\n    justify-content: center;\n    top: calc(50% - var(--control-box-size) / 2);\n    right: calc(50% - var(--control-box-size) / 2);\n    width: var(--control-box-size);\n    height: var(--control-box-size);\n    font-size: var(--icon-size);\n    background: none;\n    border: solid 2px currentColor;\n    background-color: rgb(0 0 0 /50%);\n    border-radius: var(--sl-border-radius-circle);\n    color: white;\n    pointer-events: none;\n    transition: var(--sl-transition-fast) opacity;\n  }\n\n  :host([play]:hover) .animated-image__control-box {\n    opacity: 1;\n  }\n\n  :host([play]:not(:hover)) .animated-image__control-box {\n    opacity: 0;\n  }\n\n  :host([play]) slot[name='play-icon'],\n  :host(:not([play])) slot[name='pause-icon'] {\n    display: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/animated-image/animated-image.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { clickOnElement } from '../../internal/test.js';\nimport { expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport type SlAnimatedImage from './animated-image.js';\n\ndescribe('<sl-animated-image>', () => {\n  it('should render a component', async () => {\n    const animatedImage = await fixture(html` <sl-animated-image></sl-animated-image> `);\n\n    expect(animatedImage).to.exist;\n  });\n\n  it('should render be accessible', async () => {\n    const animatedImage = await fixture(html` <sl-animated-image></sl-animated-image> `);\n\n    await expect(animatedImage).to.be.accessible();\n  });\n\n  const files = ['docs/assets/images/walk.gif', 'docs/assets/images/tie.webp'];\n\n  files.forEach((file: string) => {\n    it(`should load a ${file} without errors`, async () => {\n      const animatedImage = await fixture<SlAnimatedImage>(html` <sl-animated-image></sl-animated-image> `);\n      let errorCount = 0;\n      oneEvent(animatedImage, 'sl-error').then(() => errorCount++);\n      await loadImage(animatedImage, file);\n\n      expect(errorCount).to.be.equal(0);\n    });\n\n    it(`should play ${file} on click`, async () => {\n      const animatedImage = await fixture<SlAnimatedImage>(html` <sl-animated-image></sl-animated-image> `);\n      await loadImage(animatedImage, file);\n\n      expect(animatedImage.play).not.to.be.true;\n\n      await clickOnElement(animatedImage);\n\n      expect(animatedImage.play).to.be.true;\n    });\n\n    it(`should pause and resume ${file} on click`, async () => {\n      const animatedImage = await fixture<SlAnimatedImage>(html` <sl-animated-image></sl-animated-image> `);\n      await loadImage(animatedImage, file);\n\n      animatedImage.play = true;\n\n      await clickOnElement(animatedImage);\n\n      expect(animatedImage.play).to.be.false;\n\n      await clickOnElement(animatedImage);\n\n      expect(animatedImage.play).to.be.true;\n    });\n  });\n\n  it('should emit an error event on invalid url', async () => {\n    const animatedImage = await fixture<SlAnimatedImage>(html` <sl-animated-image></sl-animated-image> `);\n\n    const errorPromise = oneEvent(animatedImage, 'sl-error');\n    animatedImage.src = 'completelyWrong';\n\n    await errorPromise;\n  });\n});\nasync function loadImage(animatedImage: SlAnimatedImage, file: string) {\n  const loadingPromise = oneEvent(animatedImage, 'sl-load');\n  animatedImage.src = file;\n  await loadingPromise;\n}\n"
  },
  {
    "path": "src/components/animated-image/animated-image.ts",
    "content": "import SlAnimatedImage from './animated-image.component.js';\n\nexport * from './animated-image.component.js';\nexport default SlAnimatedImage;\n\nSlAnimatedImage.define('sl-animated-image');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-animated-image': SlAnimatedImage;\n  }\n}\n"
  },
  {
    "path": "src/components/animation/animation.component.ts",
    "content": "import { animations } from './animations.js';\nimport { html } from 'lit';\nimport { property, queryAsync } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './animation.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).\n * @documentation https://shoelace.style/components/animation\n * @status stable\n * @since 2.0\n *\n * @event sl-cancel - Emitted when the animation is canceled.\n * @event sl-finish - Emitted when the animation finishes.\n * @event sl-start - Emitted when the animation starts or restarts.\n *\n * @slot - The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To\n *  animate multiple elements, either wrap them in a single container or use multiple `<sl-animation>` elements.\n */\nexport default class SlAnimation extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private animation?: Animation;\n  private hasStarted = false;\n\n  @queryAsync('slot') defaultSlot: Promise<HTMLSlotElement>;\n\n  /** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */\n  @property() name = 'none';\n\n  /**\n   * Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when\n   * the animation finishes or gets canceled.\n   */\n  @property({ type: Boolean, reflect: true }) play = false;\n\n  /** The number of milliseconds to delay the start of the animation. */\n  @property({ type: Number }) delay = 0;\n\n  /**\n   * Determines the direction of playback as well as the behavior when reaching the end of an iteration.\n   * [Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)\n   */\n  @property() direction: PlaybackDirection = 'normal';\n\n  /** The number of milliseconds each iteration of the animation takes to complete. */\n  @property({ type: Number }) duration = 1000;\n\n  /**\n   * The easing function to use for the animation. This can be a Shoelace easing function or a custom easing function\n   * such as `cubic-bezier(0, 1, .76, 1.14)`.\n   */\n  @property() easing = 'linear';\n\n  /** The number of milliseconds to delay after the active period of an animation sequence. */\n  @property({ attribute: 'end-delay', type: Number }) endDelay = 0;\n\n  /** Sets how the animation applies styles to its target before and after its execution. */\n  @property() fill: FillMode = 'auto';\n\n  /** The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops. */\n  @property({ type: Number }) iterations = Infinity;\n\n  /** The offset at which to start the animation, usually between 0 (start) and 1 (end). */\n  @property({ attribute: 'iteration-start', type: Number }) iterationStart = 0;\n\n  /** The keyframes to use for the animation. If this is set, `name` will be ignored. */\n  @property({ attribute: false }) keyframes?: Keyframe[];\n\n  /**\n   * Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this\n   * to `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This\n   * value can be changed without causing the animation to restart.\n   */\n  @property({ attribute: 'playback-rate', type: Number }) playbackRate = 1;\n\n  /** Gets and sets the current animation time. */\n  get currentTime(): CSSNumberish {\n    return this.animation?.currentTime ?? 0;\n  }\n\n  set currentTime(time: number) {\n    if (this.animation) {\n      this.animation.currentTime = time;\n    }\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.createAnimation();\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.destroyAnimation();\n  }\n\n  private handleAnimationFinish = () => {\n    this.play = false;\n    this.hasStarted = false;\n    this.emit('sl-finish');\n  };\n\n  private handleAnimationCancel = () => {\n    this.play = false;\n    this.hasStarted = false;\n    this.emit('sl-cancel');\n  };\n\n  private handleSlotChange() {\n    this.destroyAnimation();\n    this.createAnimation();\n  }\n\n  private async createAnimation() {\n    const easing = animations.easings[this.easing] ?? this.easing;\n    const keyframes = this.keyframes ?? (animations as unknown as Partial<Record<string, Keyframe[]>>)[this.name];\n    const slot = await this.defaultSlot;\n    const element = slot.assignedElements()[0] as HTMLElement | undefined;\n\n    if (!element || !keyframes) {\n      return false;\n    }\n\n    this.destroyAnimation();\n    this.animation = element.animate(keyframes, {\n      delay: this.delay,\n      direction: this.direction,\n      duration: this.duration,\n      easing,\n      endDelay: this.endDelay,\n      fill: this.fill,\n      iterationStart: this.iterationStart,\n      iterations: this.iterations\n    });\n    this.animation.playbackRate = this.playbackRate;\n    this.animation.addEventListener('cancel', this.handleAnimationCancel);\n    this.animation.addEventListener('finish', this.handleAnimationFinish);\n\n    if (this.play) {\n      this.hasStarted = true;\n      this.emit('sl-start');\n    } else {\n      this.animation.pause();\n    }\n\n    return true;\n  }\n\n  private destroyAnimation() {\n    if (this.animation) {\n      this.animation.cancel();\n      this.animation.removeEventListener('cancel', this.handleAnimationCancel);\n      this.animation.removeEventListener('finish', this.handleAnimationFinish);\n      this.hasStarted = false;\n    }\n  }\n\n  @watch([\n    'name',\n    'delay',\n    'direction',\n    'duration',\n    'easing',\n    'endDelay',\n    'fill',\n    'iterations',\n    'iterationsStart',\n    'keyframes'\n  ])\n  handleAnimationChange() {\n    if (!this.hasUpdated) {\n      return;\n    }\n\n    this.createAnimation();\n  }\n\n  @watch('play')\n  handlePlayChange() {\n    if (this.animation) {\n      if (this.play && !this.hasStarted) {\n        this.hasStarted = true;\n        this.emit('sl-start');\n      }\n\n      if (this.play) {\n        this.animation.play();\n      } else {\n        this.animation.pause();\n      }\n\n      return true;\n    }\n    return false;\n  }\n\n  @watch('playbackRate')\n  handlePlaybackRateChange() {\n    if (this.animation) {\n      this.animation.playbackRate = this.playbackRate;\n    }\n  }\n\n  /** Clears all keyframe effects caused by this animation and aborts its playback. */\n  cancel() {\n    this.animation?.cancel();\n  }\n\n  /** Sets the playback time to the end of the animation corresponding to the current playback direction. */\n  finish() {\n    this.animation?.finish();\n  }\n\n  render() {\n    return html` <slot @slotchange=${this.handleSlotChange}></slot> `;\n  }\n}\n"
  },
  {
    "path": "src/components/animation/animation.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: contents;\n  }\n`;\n"
  },
  {
    "path": "src/components/animation/animation.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, oneEvent } from '@open-wc/testing';\nimport { html } from 'lit';\nimport type SlAnimation from './animation.js';\n\ndescribe('<sl-animation>', () => {\n  const boxToAnimate = html`<div style=\"width: 10px; height: 10px;\" data-testid=\"animated-box\"></div>`;\n\n  it('renders', async () => {\n    const animationContainer = await fixture<SlAnimation>(html`<sl-animation>${boxToAnimate}</sl-animation>`);\n\n    expect(animationContainer).to.exist;\n  });\n\n  it('is accessible', async () => {\n    const animationContainer = await fixture<SlAnimation>(html`<sl-animation>${boxToAnimate}</sl-animation>`);\n\n    await expect(animationContainer).to.be.accessible();\n  });\n\n  describe('animation start', () => {\n    it('does not start the animation by default', async () => {\n      const animationContainer = await fixture<SlAnimation>(\n        html`<sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"10\">${boxToAnimate}</sl-animation>`\n      );\n      await aTimeout(0);\n\n      expect(animationContainer.play).to.be.false;\n    });\n\n    it('emits the correct event on animation start', async () => {\n      const animationContainer = await fixture<SlAnimation>(\n        html`<sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"10\">${boxToAnimate}</sl-animation>`\n      );\n\n      const startPromise = oneEvent(animationContainer, 'sl-start');\n      animationContainer.play = true;\n      return startPromise;\n    });\n  });\n\n  it('emits the correct event on animation end', async () => {\n    const animationContainer = await fixture<SlAnimation>(\n      html`<sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"1\">${boxToAnimate}</sl-animation>`\n    );\n\n    const endPromise = oneEvent(animationContainer, 'sl-finish');\n    animationContainer.iterations = 1;\n    animationContainer.play = true;\n    return endPromise;\n  });\n\n  it('can be finished by hand', async () => {\n    const animationContainer = await fixture<SlAnimation>(\n      html`<sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"1000\">${boxToAnimate}</sl-animation>`\n    );\n\n    const endPromise = oneEvent(animationContainer, 'sl-finish');\n    animationContainer.iterations = 1;\n    animationContainer.play = true;\n\n    await aTimeout(0);\n\n    animationContainer.finish();\n    return endPromise;\n  });\n\n  it('can be cancelled', async () => {\n    const animationContainer = await fixture<SlAnimation>(\n      html`<sl-animation name=\"bounce\" easing=\"ease-in-out\" duration=\"1\">${boxToAnimate}</sl-animation>`\n    );\n    let animationHasFinished = false;\n    oneEvent(animationContainer, 'sl-finish').then(() => (animationHasFinished = true));\n    const cancelPromise = oneEvent(animationContainer, 'sl-cancel');\n    animationContainer.play = true;\n\n    await aTimeout(0);\n    animationContainer.cancel();\n\n    await cancelPromise;\n    expect(animationHasFinished).to.be.false;\n  });\n});\n"
  },
  {
    "path": "src/components/animation/animation.ts",
    "content": "import SlAnimation from './animation.component.js';\n\nexport * from './animation.component.js';\nexport default SlAnimation;\n\nSlAnimation.define('sl-animation');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-animation': SlAnimation;\n  }\n}\n"
  },
  {
    "path": "src/components/animation/animations.ts",
    "content": "import * as animations from '@shoelace-style/animations';\n\nexport { animations };\n\n/** Gets a list of all supported animation names. */\nexport function getAnimationNames() {\n  return Object.entries(animations)\n    .filter(([name]) => name !== 'easings')\n    .map(([name]) => name);\n}\n\n/** Gets a list of all supported easing function names. */\nexport function getEasingNames() {\n  return Object.entries(animations.easings).map(([name]) => name);\n}\n"
  },
  {
    "path": "src/components/avatar/avatar.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './avatar.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Avatars are used to represent a person or object.\n * @documentation https://shoelace.style/components/avatar\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @event sl-error - The image could not be loaded. This may because of an invalid URL, a temporary network condition, or some\n * unknown cause.\n *\n * @slot icon - The default icon to use when no image or initials are present. Works best with `<sl-icon>`.\n *\n * @csspart base - The component's base wrapper.\n * @csspart icon - The container that wraps the avatar's icon.\n * @csspart initials - The container that wraps the avatar's initials.\n * @csspart image - The avatar image. Only shown when the `image` attribute is set.\n *\n * @cssproperty --size - The size of the avatar.\n */\nexport default class SlAvatar extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-icon': SlIcon\n  };\n\n  @state() private hasError = false;\n\n  /** The image source to use for the avatar. */\n  @property() image = '';\n\n  /** A label to use to describe the avatar to assistive devices. */\n  @property() label = '';\n\n  /** Initials to use as a fallback when no image is available (1-2 characters max recommended). */\n  @property() initials = '';\n\n  /** Indicates how the browser should load the image. */\n  @property() loading: 'eager' | 'lazy' = 'eager';\n\n  /** The shape of the avatar. */\n  @property({ reflect: true }) shape: 'circle' | 'square' | 'rounded' = 'circle';\n\n  @watch('image')\n  handleImageChange() {\n    // Reset the error when a new image is provided\n    this.hasError = false;\n  }\n\n  private handleImageLoadError() {\n    this.hasError = true;\n    this.emit('sl-error');\n  }\n\n  render() {\n    const avatarWithImage = html`\n      <img\n        part=\"image\"\n        class=\"avatar__image\"\n        src=\"${this.image}\"\n        loading=\"${this.loading}\"\n        alt=\"\"\n        @error=\"${this.handleImageLoadError}\"\n      />\n    `;\n\n    let avatarWithoutImage = html``;\n\n    if (this.initials) {\n      avatarWithoutImage = html`<div part=\"initials\" class=\"avatar__initials\">${this.initials}</div>`;\n    } else {\n      avatarWithoutImage = html`\n        <div part=\"icon\" class=\"avatar__icon\" aria-hidden=\"true\">\n          <slot name=\"icon\">\n            <sl-icon name=\"person-fill\" library=\"system\"></sl-icon>\n          </slot>\n        </div>\n      `;\n    }\n\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          avatar: true,\n          'avatar--circle': this.shape === 'circle',\n          'avatar--rounded': this.shape === 'rounded',\n          'avatar--square': this.shape === 'square'\n        })}\n        role=\"img\"\n        aria-label=${this.label}\n      >\n        ${this.image && !this.hasError ? avatarWithImage : avatarWithoutImage}\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/avatar/avatar.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n\n    --size: 3rem;\n  }\n\n  .avatar {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    position: relative;\n    width: var(--size);\n    height: var(--size);\n    background-color: var(--sl-color-neutral-400);\n    font-family: var(--sl-font-sans);\n    font-size: calc(var(--size) * 0.5);\n    font-weight: var(--sl-font-weight-normal);\n    color: var(--sl-color-neutral-0);\n    user-select: none;\n    -webkit-user-select: none;\n    vertical-align: middle;\n  }\n\n  .avatar--circle,\n  .avatar--circle .avatar__image {\n    border-radius: var(--sl-border-radius-circle);\n  }\n\n  .avatar--rounded,\n  .avatar--rounded .avatar__image {\n    border-radius: var(--sl-border-radius-medium);\n  }\n\n  .avatar--square {\n    border-radius: 0;\n  }\n\n  .avatar__icon {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n  }\n\n  .avatar__initials {\n    line-height: 1;\n    text-transform: uppercase;\n  }\n\n  .avatar__image {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n    overflow: hidden;\n  }\n`;\n"
  },
  {
    "path": "src/components/avatar/avatar.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport type SlAvatar from './avatar.js';\n\n// The default avatar background just misses AA contrast, but the next step up is way too dark. Since avatars aren't\n// used to display text, we're going to relax this rule.\nconst ignoredRules = ['color-contrast'];\n\ndescribe('<sl-avatar>', () => {\n  let el: SlAvatar;\n\n  describe('when provided no parameters', () => {\n    before(async () => {\n      el = await fixture<SlAvatar>(html` <sl-avatar label=\"Avatar\"></sl-avatar> `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should default to circle styling', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(el.getAttribute('shape')).to.eq('circle');\n      expect(part.classList.value.trim()).to.eq('avatar avatar--circle');\n    });\n  });\n\n  describe('when provided an image and label parameter', () => {\n    const image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n    const label = 'Small transparent square';\n    before(async () => {\n      el = await fixture<SlAvatar>(html`<sl-avatar image=\"${image}\" label=\"${label}\"></sl-avatar>`);\n    });\n\n    it('should pass accessibility tests', async () => {\n      /**\n       * The image element itself is ancillary, because it's parent container contains the\n       * aria-label which dictates what \"sl-avatar\" is. This also implies that label text will\n       * resolve to \"\" when not provided and ignored by readers. This is why we use alt=\"\" on\n       * the image element to pass accessibility.\n       * https://html.spec.whatwg.org/multipage/images.html#ancillary-images\n       */\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('renders \"image\" part, with src and a role of presentation', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"image\"]')!;\n\n      expect(part.getAttribute('src')).to.eq(image);\n    });\n\n    it('renders the label attribute in the \"base\" part', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n\n      expect(part.getAttribute('aria-label')).to.eq(label);\n    });\n  });\n\n  describe('when provided initials parameter', () => {\n    const initials = 'SL';\n    before(async () => {\n      el = await fixture<SlAvatar>(html`<sl-avatar initials=\"${initials}\" label=\"Avatar\"></sl-avatar>`);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('renders \"initials\" part, with initials as the text node', () => {\n      const part = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"initials\"]')!;\n\n      expect(part.innerText).to.eq(initials);\n    });\n  });\n\n  describe('when image is present, the initials or icon part should not render', () => {\n    const initials = 'SL';\n    const image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n    const label = 'Small transparent square';\n    before(async () => {\n      el = await fixture<SlAvatar>(\n        html`<sl-avatar image=\"${image}\" label=\"${label}\" initials=\"${initials}\"></sl-avatar>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      /**\n       * The image element itself is ancillary, because it's parent container contains the\n       * aria-label which dictates what \"sl-avatar\" is. This also implies that label text will\n       * resolve to \"\" when not provided and ignored by readers. This is why we use alt=\"\" on\n       * the image element to pass accessibility.\n       * https://html.spec.whatwg.org/multipage/images.html#ancillary-images\n       */\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('renders \"image\" part, with src and a role of presentation', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"image\"]')!;\n\n      expect(part.getAttribute('src')).to.eq(image);\n    });\n\n    it('should not render the initials part', () => {\n      const part = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"initials\"]')!;\n\n      expect(part).to.not.exist;\n    });\n\n    it('should not render the icon part', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=icon]')!;\n\n      expect(slot).to.not.exist;\n    });\n  });\n\n  ['square', 'rounded', 'circle'].forEach(shape => {\n    describe(`when passed a shape attribute ${shape}`, () => {\n      before(async () => {\n        el = await fixture<SlAvatar>(html`<sl-avatar shape=\"${shape}\" label=\"Shaped avatar\"></sl-avatar>`);\n      });\n\n      it('should pass accessibility tests', async () => {\n        await expect(el).to.be.accessible({ ignoredRules });\n      });\n\n      it('appends the appropriate class on the \"base\" part', () => {\n        const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n\n        expect(el.getAttribute('shape')).to.eq(shape);\n        expect(part.classList.value.trim()).to.eq(`avatar avatar--${shape}`);\n      });\n    });\n  });\n\n  describe('when passed a <span>, on slot \"icon\"', () => {\n    before(async () => {\n      el = await fixture<SlAvatar>(html`<sl-avatar label=\"Avatar\"><span slot=\"icon\">random content</span></sl-avatar>`);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should accept as an assigned child in the shadow root', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=icon]')!;\n      const childNodes = slot.assignedNodes({ flatten: true }) as HTMLElement[];\n\n      expect(childNodes.length).to.eq(1);\n\n      const span = childNodes[0];\n      expect(span.innerHTML).to.eq('random content');\n    });\n  });\n\n  it('should not render the image when the image fails to load', async () => {\n    el = await fixture<SlAvatar>(html`<sl-avatar></sl-avatar>`);\n    el.image = 'bad_image';\n\n    let wasEventCalled = false;\n    el.addEventListener('sl-error', () => {\n      wasEventCalled = true;\n    });\n\n    await aTimeout(0);\n\n    await waitUntil(() => el.shadowRoot!.querySelector('img') === null);\n    expect(el.shadowRoot!.querySelector('img')).to.be.null;\n    expect(wasEventCalled).to.be.ok;\n  });\n\n  it('should show a valid image after being passed an invalid image initially', async () => {\n    el = await fixture<SlAvatar>(html`<sl-avatar></sl-avatar>`);\n\n    await aTimeout(0);\n    await waitUntil(() => el.shadowRoot!.querySelector('img') === null);\n\n    el.image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n    await el.updateComplete;\n\n    expect(el.shadowRoot?.querySelector('img')).to.exist;\n  });\n});\n"
  },
  {
    "path": "src/components/avatar/avatar.ts",
    "content": "import SlAvatar from './avatar.component.js';\n\nexport * from './avatar.component.js';\nexport default SlAvatar;\n\nSlAvatar.define('sl-avatar');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-avatar': SlAvatar;\n  }\n}\n"
  },
  {
    "path": "src/components/badge/badge.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './badge.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Badges are used to draw attention and display statuses or counts.\n * @documentation https://shoelace.style/components/badge\n * @status stable\n * @since 2.0\n *\n * @slot - The badge's content.\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlBadge extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  /** The badge's theme variant. */\n  @property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' = 'primary';\n\n  /** Draws a pill-style badge with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  /** Makes the badge pulsate to draw attention. */\n  @property({ type: Boolean, reflect: true }) pulse = false;\n\n  render() {\n    return html`\n      <span\n        part=\"base\"\n        class=${classMap({\n          badge: true,\n          'badge--primary': this.variant === 'primary',\n          'badge--success': this.variant === 'success',\n          'badge--neutral': this.variant === 'neutral',\n          'badge--warning': this.variant === 'warning',\n          'badge--danger': this.variant === 'danger',\n          'badge--pill': this.pill,\n          'badge--pulse': this.pulse\n        })}\n        role=\"status\"\n      >\n        <slot></slot>\n      </span>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/badge/badge.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-flex;\n  }\n\n  .badge {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    font-size: max(12px, 0.75em);\n    font-weight: var(--sl-font-weight-semibold);\n    letter-spacing: var(--sl-letter-spacing-normal);\n    line-height: 1;\n    border-radius: var(--sl-border-radius-small);\n    border: solid 1px var(--sl-color-neutral-0);\n    white-space: nowrap;\n    padding: 0.35em 0.6em;\n    user-select: none;\n    -webkit-user-select: none;\n    cursor: inherit;\n  }\n\n  /* Variant modifiers */\n  .badge--primary {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .badge--success {\n    background-color: var(--sl-color-success-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .badge--neutral {\n    background-color: var(--sl-color-neutral-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .badge--warning {\n    background-color: var(--sl-color-warning-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .badge--danger {\n    background-color: var(--sl-color-danger-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Pill modifier */\n  .badge--pill {\n    border-radius: var(--sl-border-radius-pill);\n  }\n\n  /* Pulse modifier */\n  .badge--pulse {\n    animation: pulse 1.5s infinite;\n  }\n\n  .badge--pulse.badge--primary {\n    --pulse-color: var(--sl-color-primary-600);\n  }\n\n  .badge--pulse.badge--success {\n    --pulse-color: var(--sl-color-success-600);\n  }\n\n  .badge--pulse.badge--neutral {\n    --pulse-color: var(--sl-color-neutral-600);\n  }\n\n  .badge--pulse.badge--warning {\n    --pulse-color: var(--sl-color-warning-600);\n  }\n\n  .badge--pulse.badge--danger {\n    --pulse-color: var(--sl-color-danger-600);\n  }\n\n  @keyframes pulse {\n    0% {\n      box-shadow: 0 0 0 0 var(--pulse-color);\n    }\n    70% {\n      box-shadow: 0 0 0 0.5rem transparent;\n    }\n    100% {\n      box-shadow: 0 0 0 0 transparent;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/badge/badge.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlBadge from './badge.js';\n\n// The default badge background just misses AA contrast, but the next step up is way too dark. We're going to relax this\n// rule for now.\nconst ignoredRules = ['color-contrast'];\n\ndescribe('<sl-badge>', () => {\n  let el: SlBadge;\n\n  describe('when provided no parameters', () => {\n    before(async () => {\n      el = await fixture<SlBadge>(html` <sl-badge>Badge</sl-badge> `);\n    });\n\n    it('should pass accessibility tests with a role of status on the base part.', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(part.getAttribute('role')).to.eq('status');\n    });\n\n    it('should render the child content provided', () => {\n      expect(el.innerText).to.eq('Badge');\n    });\n\n    it('should default to square styling, with the primary color', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(part.classList.value.trim()).to.eq('badge badge--primary');\n    });\n  });\n\n  describe('when provided a pill parameter', () => {\n    before(async () => {\n      el = await fixture<SlBadge>(html` <sl-badge pill>Badge</sl-badge> `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should append the pill class to the classlist to render a pill', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(part.classList.value.trim()).to.eq('badge badge--primary badge--pill');\n    });\n  });\n\n  describe('when provided a pulse parameter', () => {\n    before(async () => {\n      el = await fixture<SlBadge>(html` <sl-badge pulse>Badge</sl-badge> `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should append the pulse class to the classlist to render a pulse', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(part.classList.value.trim()).to.eq('badge badge--primary badge--pulse');\n    });\n  });\n\n  ['primary', 'success', 'neutral', 'warning', 'danger'].forEach(variant => {\n    describe(`when passed a variant attribute ${variant}`, () => {\n      before(async () => {\n        el = await fixture<SlBadge>(html`<sl-badge variant=\"${variant}\">Badge</sl-badge>`);\n      });\n\n      it('should pass accessibility tests', async () => {\n        await expect(el).to.be.accessible({ ignoredRules });\n      });\n\n      it('should default to square styling, with the primary color', () => {\n        const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n        expect(part.classList.value.trim()).to.eq(`badge badge--${variant}`);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/badge/badge.ts",
    "content": "import SlBadge from './badge.component.js';\n\nexport * from './badge.component.js';\nexport default SlBadge;\n\nSlBadge.define('sl-badge');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-badge': SlBadge;\n  }\n}\n"
  },
  {
    "path": "src/components/breadcrumb/breadcrumb.component.ts",
    "content": "import { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './breadcrumb.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js';\n\n/**\n * @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.\n * @documentation https://shoelace.style/components/breadcrumb\n * @status stable\n * @since 2.0\n *\n * @slot - One or more breadcrumb items to display.\n * @slot separator - The separator to use between breadcrumb items. Works best with `<sl-icon>`.\n *\n * @dependency sl-icon\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlBreadcrumb extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  private readonly localize = new LocalizeController(this);\n  private separatorDir = this.localize.dir();\n\n  @query('slot') defaultSlot: HTMLSlotElement;\n  @query('slot[name=\"separator\"]') separatorSlot: HTMLSlotElement;\n\n  /**\n   * The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by\n   * screen readers and other assistive devices to provide more context for users.\n   */\n  @property() label = '';\n\n  // Generates a clone of the separator element to use for each breadcrumb item\n  private getSeparator() {\n    const separator = this.separatorSlot.assignedElements({ flatten: true })[0] as HTMLElement;\n\n    // Clone it, remove ids, and slot it\n    const clone = separator.cloneNode(true) as HTMLElement;\n    [clone, ...clone.querySelectorAll('[id]')].forEach(el => el.removeAttribute('id'));\n    clone.setAttribute('data-default', '');\n    clone.slot = 'separator';\n\n    return clone;\n  }\n\n  private handleSlotChange() {\n    const items = [...this.defaultSlot.assignedElements({ flatten: true })].filter(\n      item => item.tagName.toLowerCase() === 'sl-breadcrumb-item'\n    ) as SlBreadcrumbItem[];\n\n    items.forEach((item, index) => {\n      // Append separators to each item if they don't already have one\n      const separator = item.querySelector('[slot=\"separator\"]');\n      if (separator === null) {\n        // No separator exists, add one\n        item.append(this.getSeparator());\n      } else if (separator.hasAttribute('data-default')) {\n        // A default separator exists, replace it\n        separator.replaceWith(this.getSeparator());\n      } else {\n        // The user provided a custom separator, leave it alone\n      }\n\n      // The last breadcrumb item is the \"current page\"\n      if (index === items.length - 1) {\n        item.setAttribute('aria-current', 'page');\n      } else {\n        item.removeAttribute('aria-current');\n      }\n    });\n  }\n\n  render() {\n    // We clone the separator and inject them into breadcrumb items, so we need to regenerate the default ones when\n    // directionality changes. We do this by storing the current separator direction, waiting for render, then calling\n    // the function that regenerates them.\n    if (this.separatorDir !== this.localize.dir()) {\n      this.separatorDir = this.localize.dir();\n      this.updateComplete.then(() => this.handleSlotChange());\n    }\n\n    return html`\n      <nav part=\"base\" class=\"breadcrumb\" aria-label=${this.label}>\n        <slot @slotchange=${this.handleSlotChange}></slot>\n      </nav>\n\n      <span hidden aria-hidden=\"true\">\n        <slot name=\"separator\">\n          <sl-icon name=${this.localize.dir() === 'rtl' ? 'chevron-left' : 'chevron-right'} library=\"system\"></sl-icon>\n        </slot>\n      </span>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/breadcrumb/breadcrumb.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  .breadcrumb {\n    display: flex;\n    align-items: center;\n    flex-wrap: wrap;\n  }\n`;\n"
  },
  {
    "path": "src/components/breadcrumb/breadcrumb.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlBreadcrumb from './breadcrumb.js';\n\n// The default link color just misses AA contrast, but the next step up is way too dark. Maybe we can solve this in the\n// future with a prefers-contrast media query.\nconst ignoredRules = ['color-contrast'];\n\ndescribe('<sl-breadcrumb>', () => {\n  let el: SlBreadcrumb;\n\n  describe('when provided a standard list of el-breadcrumb-item children and no parameters', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumb>(html`\n        <sl-breadcrumb>\n          <sl-breadcrumb-item>Catalog</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Clothing</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Women's</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Shirts &amp; Tops</sl-breadcrumb-item>\n        </sl-breadcrumb>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should render sl-icon as separator', () => {\n      expect(el.querySelectorAll('sl-icon').length).to.eq(4);\n    });\n\n    it('should attach aria-current \"page\" on the last breadcrumb item.', () => {\n      const breadcrumbItems = el.querySelectorAll('sl-breadcrumb-item');\n      const lastNode = breadcrumbItems[3];\n      expect(lastNode).attribute('aria-current', 'page');\n    });\n  });\n\n  describe('when provided a standard list of el-breadcrumb-item children and an element in the slot \"separator\" to support Custom Separators', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumb>(html`\n        <sl-breadcrumb>\n          <span class=\"replacement-separator\" slot=\"separator\">/</span>\n          <sl-breadcrumb-item>First</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n        </sl-breadcrumb>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n\n    it('should accept \"separator\" as an assigned child in the shadow root', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=separator]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should replace the sl-icon separator with the provided separator', () => {\n      expect(el.querySelectorAll('.replacement-separator').length).to.eq(4);\n      expect(el.querySelectorAll('sl-icon').length).to.eq(0);\n    });\n  });\n\n  describe('when provided a standard list of el-breadcrumb-item children and an element in the slot \"prefix\" to support prefix icons', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumb>(html`\n        <sl-breadcrumb>\n          <sl-breadcrumb-item>\n            <span class=\"prefix-example\" slot=\"prefix\">/</span>\n            Home\n          </sl-breadcrumb-item>\n          <sl-breadcrumb-item>First</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n        </sl-breadcrumb>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n  });\n\n  describe('when provided a standard list of el-breadcrumb-item children and an element in the slot \"suffix\" to support suffix icons', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumb>(html`\n        <sl-breadcrumb>\n          <sl-breadcrumb-item>First</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Second</sl-breadcrumb-item>\n          <sl-breadcrumb-item>Third</sl-breadcrumb-item>\n          <sl-breadcrumb-item>\n            <span class=\"prefix-example\" slot=\"suffix\">/</span>\n            Security\n          </sl-breadcrumb-item>\n        </sl-breadcrumb>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/breadcrumb/breadcrumb.ts",
    "content": "import SlBreadcrumb from './breadcrumb.component.js';\n\nexport * from './breadcrumb.component.js';\nexport default SlBreadcrumb;\n\nSlBreadcrumb.define('sl-breadcrumb');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-breadcrumb': SlBreadcrumb;\n  }\n}\n"
  },
  {
    "path": "src/components/breadcrumb-item/breadcrumb-item.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './breadcrumb-item.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Breadcrumb Items are used inside [breadcrumbs](/components/breadcrumb) to represent different links.\n * @documentation https://shoelace.style/components/breadcrumb-item\n * @status stable\n * @since 2.0\n *\n * @slot - The breadcrumb item's label.\n * @slot prefix - An optional prefix, usually an icon or icon button.\n * @slot suffix - An optional suffix, usually an icon or icon button.\n * @slot separator - The separator to use for the breadcrumb item. This will only change the separator for this item. If\n * you want to change it for all items in the group, set the separator on `<sl-breadcrumb>` instead.\n *\n * @csspart base - The component's base wrapper.\n * @csspart label - The breadcrumb item's label.\n * @csspart prefix - The container that wraps the prefix.\n * @csspart suffix - The container that wraps the suffix.\n * @csspart separator - The container that wraps the separator.\n */\nexport default class SlBreadcrumbItem extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix');\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n\n  @state() private renderType: 'button' | 'link' | 'dropdown' = 'button';\n\n  /**\n   * Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered\n   * internally. When unset, a button will be rendered instead.\n   */\n  @property() href?: string;\n\n  /** Tells the browser where to open the link. Only used when `href` is set. */\n  @property() target?: '_blank' | '_parent' | '_self' | '_top';\n\n  /** The `rel` attribute to use on the link. Only used when `href` is set. */\n  @property() rel = 'noreferrer noopener';\n\n  private setRenderType() {\n    const hasDropdown =\n      this.defaultSlot.assignedElements({ flatten: true }).filter(i => i.tagName.toLowerCase() === 'sl-dropdown')\n        .length > 0;\n\n    if (this.href) {\n      this.renderType = 'link';\n      return;\n    }\n\n    if (hasDropdown) {\n      this.renderType = 'dropdown';\n      return;\n    }\n\n    this.renderType = 'button';\n  }\n\n  @watch('href', { waitUntilFirstUpdate: true })\n  hrefChanged() {\n    this.setRenderType();\n  }\n\n  handleSlotChange() {\n    this.setRenderType();\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          'breadcrumb-item': true,\n          'breadcrumb-item--has-prefix': this.hasSlotController.test('prefix'),\n          'breadcrumb-item--has-suffix': this.hasSlotController.test('suffix')\n        })}\n      >\n        <span part=\"prefix\" class=\"breadcrumb-item__prefix\">\n          <slot name=\"prefix\"></slot>\n        </span>\n\n        ${this.renderType === 'link'\n          ? html`\n              <a\n                part=\"label\"\n                class=\"breadcrumb-item__label breadcrumb-item__label--link\"\n                href=\"${this.href!}\"\n                target=\"${ifDefined(this.target ? this.target : undefined)}\"\n                rel=${ifDefined(this.target ? this.rel : undefined)}\n              >\n                <slot @slotchange=${this.handleSlotChange}></slot>\n              </a>\n            `\n          : ''}\n        ${this.renderType === 'button'\n          ? html`\n              <button part=\"label\" type=\"button\" class=\"breadcrumb-item__label breadcrumb-item__label--button\">\n                <slot @slotchange=${this.handleSlotChange}></slot>\n              </button>\n            `\n          : ''}\n        ${this.renderType === 'dropdown'\n          ? html`\n              <div part=\"label\" class=\"breadcrumb-item__label breadcrumb-item__label--drop-down\">\n                <slot @slotchange=${this.handleSlotChange}></slot>\n              </div>\n            `\n          : ''}\n\n        <span part=\"suffix\" class=\"breadcrumb-item__suffix\">\n          <slot name=\"suffix\"></slot>\n        </span>\n\n        <span part=\"separator\" class=\"breadcrumb-item__separator\" aria-hidden=\"true\">\n          <slot name=\"separator\"></slot>\n        </span>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/breadcrumb-item/breadcrumb-item.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-flex;\n  }\n\n  .breadcrumb-item {\n    display: inline-flex;\n    align-items: center;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-small);\n    font-weight: var(--sl-font-weight-semibold);\n    color: var(--sl-color-neutral-600);\n    line-height: var(--sl-line-height-normal);\n    white-space: nowrap;\n  }\n\n  .breadcrumb-item__label {\n    display: inline-block;\n    font-family: inherit;\n    font-size: inherit;\n    font-weight: inherit;\n    line-height: inherit;\n    text-decoration: none;\n    color: inherit;\n    background: none;\n    border: none;\n    border-radius: var(--sl-border-radius-medium);\n    padding: 0;\n    margin: 0;\n    cursor: pointer;\n    transition: var(--sl-transition-fast) --color;\n  }\n\n  :host(:not(:last-of-type)) .breadcrumb-item__label {\n    color: var(--sl-color-primary-600);\n  }\n\n  :host(:not(:last-of-type)) .breadcrumb-item__label:hover {\n    color: var(--sl-color-primary-500);\n  }\n\n  :host(:not(:last-of-type)) .breadcrumb-item__label:active {\n    color: var(--sl-color-primary-600);\n  }\n\n  .breadcrumb-item__label:focus {\n    outline: none;\n  }\n\n  .breadcrumb-item__label:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .breadcrumb-item__prefix,\n  .breadcrumb-item__suffix {\n    display: none;\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n  }\n\n  .breadcrumb-item--has-prefix .breadcrumb-item__prefix {\n    display: inline-flex;\n    margin-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .breadcrumb-item--has-suffix .breadcrumb-item__suffix {\n    display: inline-flex;\n    margin-inline-start: var(--sl-spacing-x-small);\n  }\n\n  :host(:last-of-type) .breadcrumb-item__separator {\n    display: none;\n  }\n\n  .breadcrumb-item__separator {\n    display: inline-flex;\n    align-items: center;\n    margin: 0 var(--sl-spacing-x-small);\n    user-select: none;\n    -webkit-user-select: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/breadcrumb-item/breadcrumb-item.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlBreadcrumbItem from './breadcrumb-item.js';\n\ndescribe('<sl-breadcrumb-item>', () => {\n  let el: SlBreadcrumbItem;\n\n  describe('when not provided a href attribute', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumbItem>(html` <sl-breadcrumb-item>Home</sl-breadcrumb-item> `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should hide the separator from screen readers', () => {\n      const separator = el.shadowRoot!.querySelector<HTMLSpanElement>('[part~=\"separator\"]');\n      expect(separator).attribute('aria-hidden', 'true');\n    });\n\n    it('should render a HTMLButtonElement as the part \"label\", with a set type \"button\"', () => {\n      const button = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"label\"]');\n      expect(button).to.exist;\n      expect(button).attribute('type', 'button');\n    });\n  });\n\n  describe('when provided a href attribute', () => {\n    describe('and no target', () => {\n      before(async () => {\n        el = await fixture<SlBreadcrumbItem>(html`\n          <sl-breadcrumb-item href=\"https://jsonplaceholder.typicode.com/\">Home</sl-breadcrumb-item>\n        `);\n      });\n\n      it('should pass accessibility tests', async () => {\n        await expect(el).to.be.accessible();\n      });\n\n      it('should render a HTMLAnchorElement as the part \"label\", with the supplied href value', () => {\n        const hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~=\"label\"]');\n        expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');\n      });\n    });\n\n    describe('and target, without rel', () => {\n      before(async () => {\n        el = await fixture<SlBreadcrumbItem>(html`\n          <sl-breadcrumb-item href=\"https://jsonplaceholder.typicode.com/\" target=\"_blank\">Help</sl-breadcrumb-item>\n        `);\n      });\n\n      it('should pass accessibility tests', async () => {\n        await expect(el).to.be.accessible();\n      });\n\n      describe('should render a HTMLAnchorElement as the part \"label\"', () => {\n        let hyperlink: HTMLAnchorElement | null;\n\n        before(() => {\n          hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~=\"label\"]');\n        });\n\n        it('should use the supplied href value, as the href attribute value', () => {\n          expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');\n        });\n\n        it('should default rel attribute to \"noreferrer noopener\"', () => {\n          expect(hyperlink).attribute('rel', 'noreferrer noopener');\n        });\n      });\n    });\n\n    describe('and target, with rel', () => {\n      before(async () => {\n        el = await fixture<SlBreadcrumbItem>(html`\n          <sl-breadcrumb-item href=\"https://jsonplaceholder.typicode.com/\" target=\"_blank\" rel=\"alternate\"\n            >Help</sl-breadcrumb-item\n          >\n        `);\n      });\n\n      it('should pass accessibility tests', async () => {\n        await expect(el).to.be.accessible();\n      });\n\n      describe('should render a HTMLAnchorElement', () => {\n        let hyperlink: HTMLAnchorElement | null;\n\n        before(() => {\n          hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('a');\n        });\n\n        it('should use the supplied href value, as the href attribute value', () => {\n          expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');\n        });\n\n        it('should use the supplied rel value, as the rel attribute value', () => {\n          expect(hyperlink).attribute('rel', 'alternate');\n        });\n      });\n    });\n  });\n\n  describe('when rendering a sl-dropdown in the default slot', () => {\n    it('should not render a link or button tag, but a div wrapper', async () => {\n      el = await fixture<SlBreadcrumbItem>(html`\n        <sl-breadcrumb-item>\n          <sl-dropdown>\n            <sl-button slot=\"trigger\" size=\"small\" circle>\n              <sl-icon label=\"More options\" name=\"three-dots\"></sl-icon>\n            </sl-button>\n            <sl-menu>\n              <sl-menu-item type=\"checkbox\" checked>Web Design</sl-menu-item>\n              <sl-menu-item type=\"checkbox\">Web Development</sl-menu-item>\n              <sl-menu-item type=\"checkbox\">Marketing</sl-menu-item>\n            </sl-menu>\n          </sl-dropdown>\n        </sl-breadcrumb-item>\n      `);\n\n      await expect(el).to.be.accessible();\n      expect(el.shadowRoot!.querySelector('a')).to.be.null;\n      expect(el.shadowRoot!.querySelector('button')).to.be.null;\n      expect(el.shadowRoot!.querySelector('div.breadcrumb-item__label--drop-down')).not.to.be.null;\n    });\n  });\n\n  describe('when provided an element in the slot \"prefix\" to support prefix icons', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumbItem>(html`\n        <sl-breadcrumb-item>\n          <span class=\"prefix-example\" slot=\"prefix\">/</span>\n          Home\n        </sl-breadcrumb-item>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should accept as an assigned child in the shadow root', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=prefix]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should append class \"breadcrumb-item--has-prefix\" to \"base\" part', () => {\n      const part = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-prefix');\n    });\n  });\n\n  describe('when provided an element in the slot \"suffix\" to support suffix icons', () => {\n    before(async () => {\n      el = await fixture<SlBreadcrumbItem>(html`\n        <sl-breadcrumb-item>\n          <span class=\"prefix-example\" slot=\"suffix\">/</span>\n          Security\n        </sl-breadcrumb-item>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should accept as an assigned child in the shadow root', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=suffix]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should append class \"breadcrumb-item--has-suffix\" to \"base\" part', () => {\n      const part = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n      expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-suffix');\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/breadcrumb-item/breadcrumb-item.ts",
    "content": "import SlBreadcrumbItem from './breadcrumb-item.component.js';\n\nexport * from './breadcrumb-item.component.js';\nexport default SlBreadcrumbItem;\n\nSlBreadcrumbItem.define('sl-breadcrumb-item');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-breadcrumb-item': SlBreadcrumbItem;\n  }\n}\n"
  },
  {
    "path": "src/components/button/button.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { FormControlController, validValidityState } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html, literal } from 'lit/static-html.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlSpinner from '../spinner/spinner.component.js';\nimport styles from './button.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Buttons represent actions that are available to the user.\n * @documentation https://shoelace.style/components/button\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n * @dependency sl-spinner\n *\n * @event sl-blur - Emitted when the button loses focus.\n * @event sl-focus - Emitted when the button gains focus.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @slot - The button's label.\n * @slot prefix - A presentational prefix icon or similar element.\n * @slot suffix - A presentational suffix icon or similar element.\n *\n * @csspart base - The component's base wrapper.\n * @csspart prefix - The container that wraps the prefix.\n * @csspart label - The button's label.\n * @csspart suffix - The container that wraps the suffix.\n * @csspart caret - The button's caret icon, an `<sl-icon>` element.\n * @csspart spinner - The spinner that shows when the button is in the loading state.\n */\nexport default class SlButton extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-icon': SlIcon,\n    'sl-spinner': SlSpinner\n  };\n\n  private readonly formControlController = new FormControlController(this, {\n    assumeInteractionOn: ['click']\n  });\n\n  private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');\n  private readonly localize = new LocalizeController(this);\n\n  @query('.button') button: HTMLButtonElement | HTMLLinkElement;\n\n  @state() private hasFocus = false;\n  @state() invalid = false;\n  @property() title = ''; // make reactive to pass through\n\n  /** The button's theme variant. */\n  @property({ reflect: true }) variant: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' =\n    'default';\n\n  /** The button's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */\n  @property({ type: Boolean, reflect: true }) caret = false;\n\n  /** Disables the button. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Draws the button in a loading state. */\n  @property({ type: Boolean, reflect: true }) loading = false;\n\n  /** Draws an outlined button. */\n  @property({ type: Boolean, reflect: true }) outline = false;\n\n  /** Draws a pill-style button with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  /**\n   * Draws a circular icon button. When this attribute is present, the button expects a single `<sl-icon>` in the\n   * default slot.\n   */\n  @property({ type: Boolean, reflect: true }) circle = false;\n\n  /**\n   * The type of button. Note that the default value is `button` instead of `submit`, which is opposite of how native\n   * `<button>` elements behave. When the type is `submit`, the button will submit the surrounding form.\n   */\n  @property() type: 'button' | 'submit' | 'reset' = 'button';\n\n  /**\n   * The name of the button, submitted as a name/value pair with form data, but only when this button is the submitter.\n   * This attribute is ignored when `href` is present.\n   */\n  @property() name = '';\n\n  /**\n   * The value of the button, submitted as a pair with the button's name as part of the form data, but only when this\n   * button is the submitter. This attribute is ignored when `href` is present.\n   */\n  @property() value = '';\n\n  /** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */\n  @property() href = '';\n\n  /** Tells the browser where to open the link. Only used when `href` is present. */\n  @property() target: '_blank' | '_parent' | '_self' | '_top';\n\n  /**\n   * When using `href`, this attribute will map to the underlying link's `rel` attribute. Unlike regular links, the\n   * default is `noreferrer noopener` to prevent security exploits. However, if you're using `target` to point to a\n   * specific tab/window, this will prevent that from working correctly. You can remove or change the default value by\n   * setting the attribute to an empty string or a value of your choice, respectively.\n   */\n  @property() rel = 'noreferrer noopener';\n\n  /** Tells the browser to download the linked file as this filename. Only used when `href` is present. */\n  @property() download?: string;\n\n  /**\n   * The \"form owner\" to associate the button with. If omitted, the closest containing form will be used instead. The\n   * value of this attribute must be an id of a form in the same document or shadow root as the button.\n   */\n  @property() form: string;\n\n  /** Used to override the form owner's `action` attribute. */\n  @property({ attribute: 'formaction' }) formAction: string;\n\n  /** Used to override the form owner's `enctype` attribute.  */\n  @property({ attribute: 'formenctype' })\n  formEnctype: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';\n\n  /** Used to override the form owner's `method` attribute.  */\n  @property({ attribute: 'formmethod' }) formMethod: 'post' | 'get';\n\n  /** Used to override the form owner's `novalidate` attribute. */\n  @property({ attribute: 'formnovalidate', type: Boolean }) formNoValidate: boolean;\n\n  /** Used to override the form owner's `target` attribute. */\n  @property({ attribute: 'formtarget' }) formTarget: '_self' | '_blank' | '_parent' | '_top' | string;\n\n  /** Gets the validity state object */\n  get validity() {\n    if (this.isButton()) {\n      return (this.button as HTMLButtonElement).validity;\n    }\n\n    return validValidityState;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    if (this.isButton()) {\n      return (this.button as HTMLButtonElement).validationMessage;\n    }\n\n    return '';\n  }\n\n  firstUpdated() {\n    if (this.isButton()) {\n      this.formControlController.updateValidity();\n    }\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  private handleClick() {\n    if (this.type === 'submit') {\n      this.formControlController.submit(this);\n    }\n\n    if (this.type === 'reset') {\n      this.formControlController.reset(this);\n    }\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private isButton() {\n    return this.href ? false : true;\n  }\n\n  private isLink() {\n    return this.href ? true : false;\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    if (this.isButton()) {\n      // Disabled form controls are always valid\n      this.formControlController.setValidity(this.disabled);\n    }\n  }\n\n  /** Simulates a click on the button. */\n  click() {\n    this.button.click();\n  }\n\n  /** Sets focus on the button. */\n  focus(options?: FocusOptions) {\n    this.button.focus(options);\n  }\n\n  /** Removes focus from the button. */\n  blur() {\n    this.button.blur();\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    if (this.isButton()) {\n      return (this.button as HTMLButtonElement).checkValidity();\n    }\n\n    return true;\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    if (this.isButton()) {\n      return (this.button as HTMLButtonElement).reportValidity();\n    }\n\n    return true;\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    if (this.isButton()) {\n      (this.button as HTMLButtonElement).setCustomValidity(message);\n      this.formControlController.updateValidity();\n    }\n  }\n\n  render() {\n    const isLink = this.isLink();\n    const tag = isLink ? literal`a` : literal`button`;\n\n    /* eslint-disable lit/no-invalid-html */\n    /* eslint-disable lit/binding-positions */\n    return html`\n      <${tag}\n        part=\"base\"\n        class=${classMap({\n          button: true,\n          'button--default': this.variant === 'default',\n          'button--primary': this.variant === 'primary',\n          'button--success': this.variant === 'success',\n          'button--neutral': this.variant === 'neutral',\n          'button--warning': this.variant === 'warning',\n          'button--danger': this.variant === 'danger',\n          'button--text': this.variant === 'text',\n          'button--small': this.size === 'small',\n          'button--medium': this.size === 'medium',\n          'button--large': this.size === 'large',\n          'button--caret': this.caret,\n          'button--circle': this.circle,\n          'button--disabled': this.disabled,\n          'button--focused': this.hasFocus,\n          'button--loading': this.loading,\n          'button--standard': !this.outline,\n          'button--outline': this.outline,\n          'button--pill': this.pill,\n          'button--rtl': this.localize.dir() === 'rtl',\n          'button--has-label': this.hasSlotController.test('[default]'),\n          'button--has-prefix': this.hasSlotController.test('prefix'),\n          'button--has-suffix': this.hasSlotController.test('suffix')\n        })}\n        ?disabled=${ifDefined(isLink ? undefined : this.disabled)}\n        type=${ifDefined(isLink ? undefined : this.type)}\n        title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n        name=${ifDefined(isLink ? undefined : this.name)}\n        value=${ifDefined(isLink ? undefined : this.value)}\n        href=${ifDefined(isLink && !this.disabled ? this.href : undefined)}\n        target=${ifDefined(isLink ? this.target : undefined)}\n        download=${ifDefined(isLink ? this.download : undefined)}\n        rel=${ifDefined(isLink ? this.rel : undefined)}\n        role=${ifDefined(isLink ? undefined : 'button')}\n        aria-disabled=${this.disabled ? 'true' : 'false'}\n        tabindex=${this.disabled ? '-1' : '0'}\n        @blur=${this.handleBlur}\n        @focus=${this.handleFocus}\n        @invalid=${this.isButton() ? this.handleInvalid : null}\n        @click=${this.handleClick}\n      >\n        <slot name=\"prefix\" part=\"prefix\" class=\"button__prefix\"></slot>\n        <slot part=\"label\" class=\"button__label\"></slot>\n        <slot name=\"suffix\" part=\"suffix\" class=\"button__suffix\"></slot>\n        ${\n          this.caret ? html` <sl-icon part=\"caret\" class=\"button__caret\" library=\"system\" name=\"caret\"></sl-icon> ` : ''\n        }\n        ${this.loading ? html`<sl-spinner part=\"spinner\"></sl-spinner>` : ''}\n      </${tag}>\n    `;\n    /* eslint-enable lit/no-invalid-html */\n    /* eslint-enable lit/binding-positions */\n  }\n}\n"
  },
  {
    "path": "src/components/button/button.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n    position: relative;\n    width: auto;\n    cursor: pointer;\n  }\n\n  .button {\n    display: inline-flex;\n    align-items: stretch;\n    justify-content: center;\n    width: 100%;\n    border-style: solid;\n    border-width: var(--sl-input-border-width);\n    font-family: var(--sl-input-font-family);\n    font-weight: var(--sl-font-weight-semibold);\n    text-decoration: none;\n    user-select: none;\n    -webkit-user-select: none;\n    white-space: nowrap;\n    vertical-align: middle;\n    padding: 0;\n    transition:\n      var(--sl-transition-x-fast) background-color,\n      var(--sl-transition-x-fast) color,\n      var(--sl-transition-x-fast) border,\n      var(--sl-transition-x-fast) box-shadow;\n    cursor: inherit;\n  }\n\n  .button::-moz-focus-inner {\n    border: 0;\n  }\n\n  .button:focus {\n    outline: none;\n  }\n\n  .button:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .button--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  /* When disabled, prevent mouse events from bubbling up from children */\n  .button--disabled * {\n    pointer-events: none;\n  }\n\n  .button__prefix,\n  .button__suffix {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    pointer-events: none;\n  }\n\n  .button__label {\n    display: inline-block;\n  }\n\n  .button__label::slotted(sl-icon) {\n    vertical-align: -2px;\n  }\n\n  /*\n   * Standard buttons\n   */\n\n  /* Default */\n  .button--standard.button--default {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-input-border-color);\n    color: var(--sl-color-neutral-700);\n  }\n\n  .button--standard.button--default:hover:not(.button--disabled) {\n    background-color: var(--sl-color-primary-50);\n    border-color: var(--sl-color-primary-300);\n    color: var(--sl-color-primary-700);\n  }\n\n  .button--standard.button--default:active:not(.button--disabled) {\n    background-color: var(--sl-color-primary-100);\n    border-color: var(--sl-color-primary-400);\n    color: var(--sl-color-primary-700);\n  }\n\n  /* Primary */\n  .button--standard.button--primary {\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--primary:hover:not(.button--disabled) {\n    background-color: var(--sl-color-primary-500);\n    border-color: var(--sl-color-primary-500);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--primary:active:not(.button--disabled) {\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Success */\n  .button--standard.button--success {\n    background-color: var(--sl-color-success-600);\n    border-color: var(--sl-color-success-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--success:hover:not(.button--disabled) {\n    background-color: var(--sl-color-success-500);\n    border-color: var(--sl-color-success-500);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--success:active:not(.button--disabled) {\n    background-color: var(--sl-color-success-600);\n    border-color: var(--sl-color-success-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Neutral */\n  .button--standard.button--neutral {\n    background-color: var(--sl-color-neutral-600);\n    border-color: var(--sl-color-neutral-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--neutral:hover:not(.button--disabled) {\n    background-color: var(--sl-color-neutral-500);\n    border-color: var(--sl-color-neutral-500);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--neutral:active:not(.button--disabled) {\n    background-color: var(--sl-color-neutral-600);\n    border-color: var(--sl-color-neutral-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Warning */\n  .button--standard.button--warning {\n    background-color: var(--sl-color-warning-600);\n    border-color: var(--sl-color-warning-600);\n    color: var(--sl-color-neutral-0);\n  }\n  .button--standard.button--warning:hover:not(.button--disabled) {\n    background-color: var(--sl-color-warning-500);\n    border-color: var(--sl-color-warning-500);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--warning:active:not(.button--disabled) {\n    background-color: var(--sl-color-warning-600);\n    border-color: var(--sl-color-warning-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Danger */\n  .button--standard.button--danger {\n    background-color: var(--sl-color-danger-600);\n    border-color: var(--sl-color-danger-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--danger:hover:not(.button--disabled) {\n    background-color: var(--sl-color-danger-500);\n    border-color: var(--sl-color-danger-500);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--standard.button--danger:active:not(.button--disabled) {\n    background-color: var(--sl-color-danger-600);\n    border-color: var(--sl-color-danger-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /*\n   * Outline buttons\n   */\n\n  .button--outline {\n    background: none;\n    border: solid 1px;\n  }\n\n  /* Default */\n  .button--outline.button--default {\n    border-color: var(--sl-input-border-color);\n    color: var(--sl-color-neutral-700);\n  }\n\n  .button--outline.button--default:hover:not(.button--disabled),\n  .button--outline.button--default.button--checked:not(.button--disabled) {\n    border-color: var(--sl-color-primary-600);\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--default:active:not(.button--disabled) {\n    border-color: var(--sl-color-primary-700);\n    background-color: var(--sl-color-primary-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Primary */\n  .button--outline.button--primary {\n    border-color: var(--sl-color-primary-600);\n    color: var(--sl-color-primary-600);\n  }\n\n  .button--outline.button--primary:hover:not(.button--disabled),\n  .button--outline.button--primary.button--checked:not(.button--disabled) {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--primary:active:not(.button--disabled) {\n    border-color: var(--sl-color-primary-700);\n    background-color: var(--sl-color-primary-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Success */\n  .button--outline.button--success {\n    border-color: var(--sl-color-success-600);\n    color: var(--sl-color-success-600);\n  }\n\n  .button--outline.button--success:hover:not(.button--disabled),\n  .button--outline.button--success.button--checked:not(.button--disabled) {\n    background-color: var(--sl-color-success-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--success:active:not(.button--disabled) {\n    border-color: var(--sl-color-success-700);\n    background-color: var(--sl-color-success-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Neutral */\n  .button--outline.button--neutral {\n    border-color: var(--sl-color-neutral-600);\n    color: var(--sl-color-neutral-600);\n  }\n\n  .button--outline.button--neutral:hover:not(.button--disabled),\n  .button--outline.button--neutral.button--checked:not(.button--disabled) {\n    background-color: var(--sl-color-neutral-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--neutral:active:not(.button--disabled) {\n    border-color: var(--sl-color-neutral-700);\n    background-color: var(--sl-color-neutral-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Warning */\n  .button--outline.button--warning {\n    border-color: var(--sl-color-warning-600);\n    color: var(--sl-color-warning-600);\n  }\n\n  .button--outline.button--warning:hover:not(.button--disabled),\n  .button--outline.button--warning.button--checked:not(.button--disabled) {\n    background-color: var(--sl-color-warning-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--warning:active:not(.button--disabled) {\n    border-color: var(--sl-color-warning-700);\n    background-color: var(--sl-color-warning-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  /* Danger */\n  .button--outline.button--danger {\n    border-color: var(--sl-color-danger-600);\n    color: var(--sl-color-danger-600);\n  }\n\n  .button--outline.button--danger:hover:not(.button--disabled),\n  .button--outline.button--danger.button--checked:not(.button--disabled) {\n    background-color: var(--sl-color-danger-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  .button--outline.button--danger:active:not(.button--disabled) {\n    border-color: var(--sl-color-danger-700);\n    background-color: var(--sl-color-danger-700);\n    color: var(--sl-color-neutral-0);\n  }\n\n  @media (forced-colors: active) {\n    .button.button--outline.button--checked:not(.button--disabled) {\n      outline: solid 2px transparent;\n    }\n  }\n\n  /*\n   * Text buttons\n   */\n\n  .button--text {\n    background-color: transparent;\n    border-color: transparent;\n    color: var(--sl-color-primary-600);\n  }\n\n  .button--text:hover:not(.button--disabled) {\n    background-color: transparent;\n    border-color: transparent;\n    color: var(--sl-color-primary-500);\n  }\n\n  .button--text:focus-visible:not(.button--disabled) {\n    background-color: transparent;\n    border-color: transparent;\n    color: var(--sl-color-primary-500);\n  }\n\n  .button--text:active:not(.button--disabled) {\n    background-color: transparent;\n    border-color: transparent;\n    color: var(--sl-color-primary-700);\n  }\n\n  /*\n   * Size modifiers\n   */\n\n  .button--small {\n    height: auto;\n    min-height: var(--sl-input-height-small);\n    font-size: var(--sl-button-font-size-small);\n    line-height: calc(var(--sl-input-height-small) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-small);\n  }\n\n  .button--medium {\n    height: auto;\n    min-height: var(--sl-input-height-medium);\n    font-size: var(--sl-button-font-size-medium);\n    line-height: calc(var(--sl-input-height-medium) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-medium);\n  }\n\n  .button--large {\n    height: auto;\n    min-height: var(--sl-input-height-large);\n    font-size: var(--sl-button-font-size-large);\n    line-height: calc(var(--sl-input-height-large) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-large);\n  }\n\n  /*\n   * Pill modifier\n   */\n\n  .button--pill.button--small {\n    border-radius: var(--sl-input-height-small);\n  }\n\n  .button--pill.button--medium {\n    border-radius: var(--sl-input-height-medium);\n  }\n\n  .button--pill.button--large {\n    border-radius: var(--sl-input-height-large);\n  }\n\n  /*\n   * Circle modifier\n   */\n\n  .button--circle {\n    padding-left: 0;\n    padding-right: 0;\n  }\n\n  .button--circle.button--small {\n    width: var(--sl-input-height-small);\n    border-radius: 50%;\n  }\n\n  .button--circle.button--medium {\n    width: var(--sl-input-height-medium);\n    border-radius: 50%;\n  }\n\n  .button--circle.button--large {\n    width: var(--sl-input-height-large);\n    border-radius: 50%;\n  }\n\n  .button--circle .button__prefix,\n  .button--circle .button__suffix,\n  .button--circle .button__caret {\n    display: none;\n  }\n\n  /*\n   * Caret modifier\n   */\n\n  .button--caret .button__suffix {\n    display: none;\n  }\n\n  .button--caret .button__caret {\n    height: auto;\n  }\n\n  /*\n   * Loading modifier\n   */\n\n  .button--loading {\n    position: relative;\n    cursor: wait;\n  }\n\n  .button--loading .button__prefix,\n  .button--loading .button__label,\n  .button--loading .button__suffix,\n  .button--loading .button__caret {\n    visibility: hidden;\n  }\n\n  .button--loading sl-spinner {\n    --indicator-color: currentColor;\n    position: absolute;\n    font-size: 1em;\n    height: 1em;\n    width: 1em;\n    top: calc(50% - 0.5em);\n    left: calc(50% - 0.5em);\n  }\n\n  /*\n   * Badges\n   */\n\n  .button ::slotted(sl-badge) {\n    position: absolute;\n    top: 0;\n    right: 0;\n    translate: 50% -50%;\n    pointer-events: none;\n  }\n\n  .button--rtl ::slotted(sl-badge) {\n    right: auto;\n    left: 0;\n    translate: -50% -50%;\n  }\n\n  /*\n   * Button spacing\n   */\n\n  .button--has-label.button--small .button__label {\n    padding: 0 var(--sl-spacing-small);\n  }\n\n  .button--has-label.button--medium .button__label {\n    padding: 0 var(--sl-spacing-medium);\n  }\n\n  .button--has-label.button--large .button__label {\n    padding: 0 var(--sl-spacing-large);\n  }\n\n  .button--has-prefix.button--small {\n    padding-inline-start: var(--sl-spacing-x-small);\n  }\n\n  .button--has-prefix.button--small .button__label {\n    padding-inline-start: var(--sl-spacing-x-small);\n  }\n\n  .button--has-prefix.button--medium {\n    padding-inline-start: var(--sl-spacing-small);\n  }\n\n  .button--has-prefix.button--medium .button__label {\n    padding-inline-start: var(--sl-spacing-small);\n  }\n\n  .button--has-prefix.button--large {\n    padding-inline-start: var(--sl-spacing-small);\n  }\n\n  .button--has-prefix.button--large .button__label {\n    padding-inline-start: var(--sl-spacing-small);\n  }\n\n  .button--has-suffix.button--small,\n  .button--caret.button--small {\n    padding-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .button--has-suffix.button--small .button__label,\n  .button--caret.button--small .button__label {\n    padding-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .button--has-suffix.button--medium,\n  .button--caret.button--medium {\n    padding-inline-end: var(--sl-spacing-small);\n  }\n\n  .button--has-suffix.button--medium .button__label,\n  .button--caret.button--medium .button__label {\n    padding-inline-end: var(--sl-spacing-small);\n  }\n\n  .button--has-suffix.button--large,\n  .button--caret.button--large {\n    padding-inline-end: var(--sl-spacing-small);\n  }\n\n  .button--has-suffix.button--large .button__label,\n  .button--caret.button--large .button__label {\n    padding-inline-end: var(--sl-spacing-small);\n  }\n\n  /*\n   * Button groups support a variety of button types (e.g. buttons with tooltips, buttons as dropdown triggers, etc.).\n   * This means buttons aren't always direct descendants of the button group, thus we can't target them with the\n   * ::slotted selector. To work around this, the button group component does some magic to add these special classes to\n   * buttons and we style them here instead.\n   */\n\n  :host([data-sl-button-group__button--first]:not([data-sl-button-group__button--last])) .button {\n    border-start-end-radius: 0;\n    border-end-end-radius: 0;\n  }\n\n  :host([data-sl-button-group__button--inner]) .button {\n    border-radius: 0;\n  }\n\n  :host([data-sl-button-group__button--last]:not([data-sl-button-group__button--first])) .button {\n    border-start-start-radius: 0;\n    border-end-start-radius: 0;\n  }\n\n  /* All except the first */\n  :host([data-sl-button-group__button]:not([data-sl-button-group__button--first])) {\n    margin-inline-start: calc(-1 * var(--sl-input-border-width));\n  }\n\n  /* Add a visual separator between solid buttons */\n  :host(\n      [data-sl-button-group__button]:not(\n          [data-sl-button-group__button--first],\n          [data-sl-button-group__button--radio],\n          [variant='default']\n        ):not(:hover)\n    )\n    .button:after {\n    content: '';\n    position: absolute;\n    top: 0;\n    inset-inline-start: 0;\n    bottom: 0;\n    border-left: solid 1px rgb(128 128 128 / 33%);\n    mix-blend-mode: multiply;\n  }\n\n  /* Bump hovered, focused, and checked buttons up so their focus ring isn't clipped */\n  :host([data-sl-button-group__button--hover]) {\n    z-index: 1;\n  }\n\n  /* Focus and checked are always on top */\n  :host([data-sl-button-group__button--focus]),\n  :host([data-sl-button-group__button][checked]) {\n    z-index: 2;\n  }\n`;\n"
  },
  {
    "path": "src/components/button/button.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport sinon from 'sinon';\nimport type SlButton from './button.js';\n\nconst variants = ['default', 'primary', 'success', 'neutral', 'warning', 'danger'];\n\ndescribe('<sl-button>', () => {\n  describe('accessibility tests', () => {\n    variants.forEach(variant => {\n      it(`should be accessible when variant is \"${variant}\"`, async () => {\n        const el = await fixture<SlButton>(html` <sl-button variant=\"${variant}\"> Button Label </sl-button> `);\n        await expect(el).to.be.accessible();\n      });\n    });\n  });\n  describe('when an attribute is removed', () => {\n    it(\"should return to 'default' when attribute removed with no initial attribute\", async () => {\n      const el = await fixture<SlButton>(html`<sl-button>Button label</sl-button>`);\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n\n      el.removeAttribute('variant');\n      await el.updateComplete;\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n    });\n\n    it(\"should return to 'default' when attribute removed with an initial attribute\", async () => {\n      const el = await fixture<SlButton>(html`<sl-button variant=\"primary\">Button label</sl-button>`);\n\n      expect(el.variant).to.equal('primary');\n      expect(el.getAttribute('variant')).to.equal('primary');\n\n      el.removeAttribute('variant');\n      await el.updateComplete;\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n    });\n  });\n\n  describe('when a property is set to null', () => {\n    it(\"should return to 'default' when property set to null with no initial attribute\", async () => {\n      const el = await fixture<SlButton>(html`<sl-button>Button label</sl-button>`);\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n\n      // @ts-expect-error Its a test. Stop.\n      el.variant = null;\n      await el.updateComplete;\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n    });\n\n    it(\"should return to 'default' when property set to null with an initial attribute\", async () => {\n      const el = await fixture<SlButton>(html`<sl-button variant=\"primary\">Button label</sl-button>`);\n\n      expect(el.variant).to.equal('primary');\n      expect(el.getAttribute('variant')).to.equal('primary');\n\n      // @ts-expect-error Its a test. Stop.\n      el.variant = null;\n      await el.updateComplete;\n\n      expect(el.variant).to.equal('default');\n      expect(el.getAttribute('variant')).to.equal('default');\n    });\n  });\n\n  describe('when provided no parameters', () => {\n    it('passes accessibility test', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button Label</sl-button> `);\n      await expect(el).to.be.accessible();\n    });\n\n    it('default values are set correctly', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button Label</sl-button> `);\n\n      expect(el.title).to.equal('');\n      expect(el.variant).to.equal('default');\n      expect(el.size).to.equal('medium');\n      expect(el.disabled).to.equal(false);\n      expect(el.caret).to.equal(false);\n      expect(el.loading).to.equal(false);\n      expect(el.outline).to.equal(false);\n      expect(el.pill).to.equal(false);\n      expect(el.circle).to.equal(false);\n    });\n\n    it('should render as a <button>', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('button')).to.exist;\n      expect(el.shadowRoot!.querySelector('a')).not.to.exist;\n    });\n\n    it('should not have a spinner present', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('sl-spinner')).not.to.exist;\n    });\n\n    it('should not have a caret present', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button Label</sl-button> `);\n      expect(el.shadowRoot?.querySelector('[part~=\"caret\"]')).not.to.exist;\n    });\n  });\n\n  describe('when disabled', () => {\n    it('passes accessibility test', async () => {\n      const el = await fixture<SlButton>(html` <sl-button disabled>Button Label</sl-button> `);\n      await expect(el).to.be.accessible();\n    });\n\n    it('should disable the native <button> when rendering a <button>', async () => {\n      const el = await fixture<SlButton>(html` <sl-button disabled>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('button[disabled]')).to.exist;\n    });\n\n    it('should not disable the native <a> when rendering an <a>', async () => {\n      const el = await fixture<SlButton>(html` <sl-button href=\"some/path\" disabled>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('a[disabled]')).not.to.exist;\n    });\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlButton>(html` <sl-button title=\"Test\"></sl-button> `);\n    const button = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"base\"]')!;\n\n    expect(button.title).to.equal('Test');\n  });\n\n  describe('when loading', () => {\n    it('should have a spinner present', async () => {\n      const el = await fixture<SlButton>(html` <sl-button loading>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('sl-spinner')).to.exist;\n    });\n  });\n\n  describe('when caret', () => {\n    it('should have a caret present', async () => {\n      const el = await fixture<SlButton>(html` <sl-button caret>Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('[part~=\"caret\"]')).to.exist;\n    });\n  });\n\n  describe('when href is present', () => {\n    it('should render as an <a>', async () => {\n      const el = await fixture<SlButton>(html` <sl-button href=\"some/path\">Button Label</sl-button> `);\n      expect(el.shadowRoot!.querySelector('a')).to.exist;\n      expect(el.shadowRoot!.querySelector('button')).not.to.exist;\n    });\n\n    it('should render a link with rel=\"noreferrer noopener\" when target is set and rel is not', async () => {\n      const el = await fixture<SlButton>(html`\n        <sl-button href=\"https://example.com/\" target=\"_blank\">Link</sl-button>\n      `);\n      const link = el.shadowRoot!.querySelector('a')!;\n      expect(link?.getAttribute('rel')).to.equal('noreferrer noopener');\n    });\n\n    it('should render a link with rel=\"\" when a target is provided and rel is empty', async () => {\n      const el = await fixture<SlButton>(html`\n        <sl-button href=\"https://example.com/\" target=\"_blank\" rel=\"\">Link</sl-button>\n      `);\n      const link = el.shadowRoot!.querySelector('a')!;\n      expect(link?.getAttribute('rel')).to.equal('');\n    });\n\n    it(`should render a link with a custom rel when a custom rel is provided`, async () => {\n      const el = await fixture<SlButton>(html`\n        <sl-button href=\"https://example.com/\" target=\"_blank\" rel=\"1\">Link</sl-button>\n      `);\n      const link = el.shadowRoot!.querySelector('a')!;\n      expect(link?.getAttribute('rel')).to.equal('1');\n    });\n  });\n\n  describe('when submitting a form', () => {\n    it('should submit when the button is inside the form', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form action=\"\" method=\"post\">\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector<SlButton>('sl-button')!;\n      const handleSubmit = sinon.spy((event: SubmitEvent) => event.preventDefault());\n\n      form.addEventListener('submit', handleSubmit);\n      button.click();\n\n      expect(handleSubmit).to.have.been.calledOnce;\n    });\n\n    it('should submit when the button is outside the form and has a form attribute', async () => {\n      const el = await fixture(html`\n        <div>\n          <form id=\"a\" action=\"\" method=\"post\"></form>\n          <sl-button type=\"submit\" form=\"a\">Submit</sl-button>\n        </div>\n      `);\n      const form = el.querySelector<HTMLFormElement>('form')!;\n      const button = el.querySelector<SlButton>('sl-button')!;\n      const handleSubmit = sinon.spy((event: SubmitEvent) => event.preventDefault());\n\n      form.addEventListener('submit', handleSubmit);\n      button.click();\n\n      expect(handleSubmit).to.have.been.calledOnce;\n    });\n\n    it('should override form attributes when formaction, formmethod, formnovalidate, and formtarget are used inside a form', async () => {\n      const form = await fixture(html`\n        <form id=\"a\" action=\"foo\" method=\"post\" target=\"_self\">\n          <sl-button type=\"submit\" form=\"a\" formaction=\"bar\" formmethod=\"get\" formtarget=\"_blank\" formnovalidate>\n            Submit\n          </sl-button>\n        </form>\n      `);\n      const button = form.querySelector<SlButton>('sl-button')!;\n      const handleSubmit = sinon.spy((event: SubmitEvent) => {\n        submitter = event.submitter as HTMLButtonElement;\n        event.preventDefault();\n      });\n      let submitter!: HTMLButtonElement;\n\n      form.addEventListener('submit', handleSubmit);\n      button.click();\n\n      expect(handleSubmit).to.have.been.calledOnce;\n      expect(submitter.formAction.endsWith('/bar')).to.be.true;\n      expect(submitter.formMethod).to.equal('get');\n      expect(submitter.formTarget).to.equal('_blank');\n      expect(submitter.formNoValidate).to.be.true;\n    });\n\n    it('should override form attributes when formaction, formmethod, formnovalidate, and formtarget are used outside a form', async () => {\n      const el = await fixture(html`\n        <div>\n          <form id=\"a\" action=\"foo\" method=\"post\" target=\"_self\"></form>\n          <sl-button type=\"submit\" form=\"a\" formaction=\"bar\" formmethod=\"get\" formtarget=\"_blank\" formnovalidate>\n            Submit\n          </sl-button>\n        </div>\n      `);\n      const form = el.querySelector<HTMLFormElement>('form')!;\n      const button = el.querySelector<SlButton>('sl-button')!;\n      const handleSubmit = sinon.spy((event: SubmitEvent) => {\n        submitter = event.submitter as HTMLButtonElement;\n        event.preventDefault();\n      });\n      let submitter!: HTMLButtonElement;\n\n      form.addEventListener('submit', handleSubmit);\n      button.click();\n\n      expect(handleSubmit).to.have.been.calledOnce;\n      expect(submitter.formAction.endsWith('/bar')).to.be.true;\n      expect(submitter.formMethod).to.equal('get');\n      expect(submitter.formTarget).to.equal('_blank');\n      expect(submitter.formNoValidate).to.be.true;\n    });\n  });\n\n  describe('when using methods', () => {\n    it('should emit sl-focus and sl-blur when the button is focused and blurred', async () => {\n      const el = await fixture<SlButton>(html` <sl-button>Button</sl-button> `);\n      const focusHandler = sinon.spy();\n      const blurHandler = sinon.spy();\n\n      el.addEventListener('sl-focus', focusHandler);\n      el.addEventListener('sl-blur', blurHandler);\n\n      el.focus();\n      await waitUntil(() => focusHandler.calledOnce);\n\n      el.blur();\n      await waitUntil(() => blurHandler.calledOnce);\n\n      expect(focusHandler).to.have.been.calledOnce;\n      expect(blurHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit a click event when calling click()', async () => {\n      const el = await fixture<SlButton>(html` <sl-button></sl-button> `);\n      const clickHandler = sinon.spy();\n\n      el.addEventListener('click', clickHandler);\n      el.click();\n      await waitUntil(() => clickHandler.calledOnce);\n\n      expect(clickHandler).to.have.been.calledOnce;\n    });\n  });\n\n  runFormControlBaseTests({\n    tagName: 'sl-button',\n    variantName: 'type=\"button\"',\n\n    init: (control: SlButton) => {\n      control.type = 'button';\n    }\n  });\n\n  runFormControlBaseTests({\n    tagName: 'sl-button',\n    variantName: 'type=\"submit\"',\n\n    init: (control: SlButton) => {\n      control.type = 'submit';\n    }\n  });\n\n  runFormControlBaseTests({\n    tagName: 'sl-button',\n    variantName: 'href=\"xyz\"',\n\n    init: (control: SlButton) => {\n      control.href = 'some-url';\n    }\n  });\n});\n"
  },
  {
    "path": "src/components/button/button.ts",
    "content": "import SlButton from './button.component.js';\n\nexport * from './button.component.js';\nexport default SlButton;\n\nSlButton.define('sl-button');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-button': SlButton;\n  }\n}\n"
  },
  {
    "path": "src/components/button-group/button-group.component.ts",
    "content": "import { html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './button-group.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Button groups can be used to group related buttons into sections.\n * @documentation https://shoelace.style/components/button-group\n * @status stable\n * @since 2.0\n *\n * @slot - One or more `<sl-button>` elements to display in the button group.\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlButtonGroup extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  @query('slot') defaultSlot: HTMLSlotElement;\n\n  @state() disableRole = false;\n\n  /**\n   * A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive\n   * devices when interacting with the control and is strongly recommended.\n   */\n  @property() label = '';\n\n  private handleFocus(event: Event) {\n    const button = findButton(event.target as HTMLElement);\n    button?.toggleAttribute('data-sl-button-group__button--focus', true);\n  }\n\n  private handleBlur(event: Event) {\n    const button = findButton(event.target as HTMLElement);\n    button?.toggleAttribute('data-sl-button-group__button--focus', false);\n  }\n\n  private handleMouseOver(event: Event) {\n    const button = findButton(event.target as HTMLElement);\n    button?.toggleAttribute('data-sl-button-group__button--hover', true);\n  }\n\n  private handleMouseOut(event: Event) {\n    const button = findButton(event.target as HTMLElement);\n    button?.toggleAttribute('data-sl-button-group__button--hover', false);\n  }\n\n  private handleSlotChange() {\n    const slottedElements = [...this.defaultSlot.assignedElements({ flatten: true })] as HTMLElement[];\n\n    slottedElements.forEach(el => {\n      const index = slottedElements.indexOf(el);\n      const button = findButton(el);\n\n      if (button) {\n        button.toggleAttribute('data-sl-button-group__button', true);\n        button.toggleAttribute('data-sl-button-group__button--first', index === 0);\n        button.toggleAttribute('data-sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);\n        button.toggleAttribute('data-sl-button-group__button--last', index === slottedElements.length - 1);\n        button.toggleAttribute(\n          'data-sl-button-group__button--radio',\n          button.tagName.toLowerCase() === 'sl-radio-button'\n        );\n      }\n    });\n  }\n\n  render() {\n    // eslint-disable-next-line lit-a11y/mouse-events-have-key-events\n    return html`\n      <div\n        part=\"base\"\n        class=\"button-group\"\n        role=\"${this.disableRole ? 'presentation' : 'group'}\"\n        aria-label=${this.label}\n        @focusout=${this.handleBlur}\n        @focusin=${this.handleFocus}\n        @mouseover=${this.handleMouseOver}\n        @mouseout=${this.handleMouseOut}\n      >\n        <slot @slotchange=${this.handleSlotChange}></slot>\n      </div>\n    `;\n  }\n}\n\nfunction findButton(el: HTMLElement) {\n  const selector = 'sl-button, sl-radio-button';\n\n  // The button could be the target element or a child of it (e.g. a dropdown or tooltip anchor)\n  return el.closest(selector) ?? el.querySelector(selector);\n}\n"
  },
  {
    "path": "src/components/button-group/button-group.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  .button-group {\n    display: flex;\n    flex-wrap: nowrap;\n  }\n`;\n"
  },
  {
    "path": "src/components/button-group/button-group.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { elementUpdated, expect, fixture, html } from '@open-wc/testing';\nimport type SlButtonGroup from './button-group.js';\n\ndescribe('<sl-button-group>', () => {\n  describe('defaults ', () => {\n    it('passes accessibility test', async () => {\n      const group = await fixture<SlButtonGroup>(html`\n        <sl-button-group>\n          <sl-button>Button 1 Label</sl-button>\n          <sl-button>Button 2 Label</sl-button>\n          <sl-button>Button 3 Label</sl-button>\n        </sl-button-group>\n      `);\n      await expect(group).to.be.accessible();\n    });\n\n    it('default label empty', async () => {\n      const group = await fixture<SlButtonGroup>(html`\n        <sl-button-group>\n          <sl-button>Button 1 Label</sl-button>\n          <sl-button>Button 2 Label</sl-button>\n          <sl-button>Button 3 Label</sl-button>\n        </sl-button-group>\n      `);\n      expect(group.label).to.equal('');\n    });\n  });\n\n  describe('slotted button data attributes', () => {\n    it('slotted buttons have the right data attributes applied based on their order', async () => {\n      const group = await fixture<SlButtonGroup>(html`\n        <sl-button-group>\n          <sl-button>Button 1 Label</sl-button>\n          <sl-button>Button 2 Label</sl-button>\n          <sl-button>Button 3 Label</sl-button>\n        </sl-button-group>\n      `);\n\n      const allButtons = group.querySelectorAll('sl-button');\n      const hasGroupAttrib = Array.from(allButtons).every(button =>\n        button.hasAttribute('data-sl-button-group__button')\n      );\n      expect(hasGroupAttrib).to.be.true;\n\n      expect(allButtons[0]).to.have.attribute('data-sl-button-group__button--first');\n      expect(allButtons[1]).to.have.attribute('data-sl-button-group__button--inner');\n      expect(allButtons[2]).to.have.attribute('data-sl-button-group__button--last');\n    });\n  });\n\n  describe('focus and blur events', () => {\n    it('toggles focus data attribute to slotted buttons on focus/blur', async () => {\n      const group = await fixture<SlButtonGroup>(html`\n        <sl-button-group>\n          <sl-button>Button 1 Label</sl-button>\n          <sl-button>Button 2 Label</sl-button>\n          <sl-button>Button 3 Label</sl-button>\n        </sl-button-group>\n      `);\n\n      const allButtons = group.querySelectorAll('sl-button');\n      allButtons[0].dispatchEvent(new FocusEvent('focusin', { bubbles: true }));\n\n      await elementUpdated(allButtons[0]);\n      expect(allButtons[0]).to.have.attribute('data-sl-button-group__button--focus');\n\n      allButtons[0].dispatchEvent(new FocusEvent('focusout', { bubbles: true }));\n      await elementUpdated(allButtons[0]);\n      expect(allButtons[0]).to.not.have.attribute('data-sl-button-group__button--focus');\n    });\n  });\n\n  describe('mouseover and mouseout events', () => {\n    it('toggles hover data attribute to slotted buttons on mouseover/mouseout', async () => {\n      const group = await fixture<SlButtonGroup>(html`\n        <sl-button-group>\n          <sl-button>Button 1 Label</sl-button>\n          <sl-button>Button 2 Label</sl-button>\n          <sl-button>Button 3 Label</sl-button>\n        </sl-button-group>\n      `);\n\n      const allButtons = group.querySelectorAll('sl-button');\n\n      allButtons[0].dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));\n      await elementUpdated(allButtons[0]);\n      expect(allButtons[0]).to.have.attribute('data-sl-button-group__button--hover');\n\n      allButtons[0].dispatchEvent(new MouseEvent('mouseout', { bubbles: true }));\n      await elementUpdated(allButtons[0]);\n      expect(allButtons[0]).to.not.have.attribute('data-sl-button-group__button--hover');\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/button-group/button-group.ts",
    "content": "import SlButtonGroup from './button-group.component.js';\n\nexport * from './button-group.component.js';\nexport default SlButtonGroup;\n\nSlButtonGroup.define('sl-button-group');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-button-group': SlButtonGroup;\n  }\n}\n"
  },
  {
    "path": "src/components/card/card.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './card.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Cards can be used to group related subjects in a container.\n * @documentation https://shoelace.style/components/card\n * @status stable\n * @since 2.0\n *\n * @slot - The card's main content.\n * @slot header - An optional header for the card.\n * @slot footer - An optional footer for the card.\n * @slot image - An optional image to render at the start of the card.\n *\n * @csspart base - The component's base wrapper.\n * @csspart image - The container that wraps the card's image.\n * @csspart header - The container that wraps the card's header.\n * @csspart body - The container that wraps the card's main content.\n * @csspart footer - The container that wraps the card's footer.\n *\n * @cssproperty --border-color - The card's border color, including borders that occur inside the card.\n * @cssproperty --border-radius - The border radius for the card's edges.\n * @cssproperty --border-width - The width of the card's borders.\n * @cssproperty --padding - The padding to use for the card's sections.\n */\nexport default class SlCard extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          card: true,\n          'card--has-footer': this.hasSlotController.test('footer'),\n          'card--has-image': this.hasSlotController.test('image'),\n          'card--has-header': this.hasSlotController.test('header')\n        })}\n      >\n        <slot name=\"image\" part=\"image\" class=\"card__image\"></slot>\n        <slot name=\"header\" part=\"header\" class=\"card__header\"></slot>\n        <slot part=\"body\" class=\"card__body\"></slot>\n        <slot name=\"footer\" part=\"footer\" class=\"card__footer\"></slot>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/card/card.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --border-color: var(--sl-color-neutral-200);\n    --border-radius: var(--sl-border-radius-medium);\n    --border-width: 1px;\n    --padding: var(--sl-spacing-large);\n\n    display: inline-block;\n  }\n\n  .card {\n    display: flex;\n    flex-direction: column;\n    background-color: var(--sl-panel-background-color);\n    box-shadow: var(--sl-shadow-x-small);\n    border: solid var(--border-width) var(--border-color);\n    border-radius: var(--border-radius);\n  }\n\n  .card__image {\n    display: flex;\n    border-top-left-radius: var(--border-radius);\n    border-top-right-radius: var(--border-radius);\n    margin: calc(-1 * var(--border-width));\n    overflow: hidden;\n  }\n\n  .card__image::slotted(img) {\n    display: block;\n    width: 100%;\n  }\n\n  .card:not(.card--has-image) .card__image {\n    display: none;\n  }\n\n  .card__header {\n    display: block;\n    border-bottom: solid var(--border-width) var(--border-color);\n    padding: calc(var(--padding) / 2) var(--padding);\n  }\n\n  .card:not(.card--has-header) .card__header {\n    display: none;\n  }\n\n  .card:not(.card--has-image) .card__header {\n    border-top-left-radius: var(--border-radius);\n    border-top-right-radius: var(--border-radius);\n  }\n\n  .card__body {\n    display: block;\n    padding: var(--padding);\n  }\n\n  .card--has-footer .card__footer {\n    display: block;\n    border-top: solid var(--border-width) var(--border-color);\n    padding: var(--padding);\n  }\n\n  .card:not(.card--has-footer) .card__footer {\n    display: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/card/card.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlCard from './card.js';\n\ndescribe('<sl-card>', () => {\n  let el: SlCard;\n\n  describe('when provided no parameters', () => {\n    before(async () => {\n      el = await fixture<SlCard>(html`\n        <sl-card>This is just a basic card. No image, no header, and no footer. Just your content.</sl-card>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should render the child content provided.', () => {\n      expect(el.innerText).to.eq('This is just a basic card. No image, no header, and no footer. Just your content.');\n    });\n\n    it('should contain the class card.', () => {\n      const card = el.shadowRoot!.querySelector('.card')!;\n      expect(card.classList.value.trim()).to.eq('card');\n    });\n  });\n\n  describe('when provided an element in the slot \"header\" to render a header', () => {\n    before(async () => {\n      el = await fixture<SlCard>(\n        html`<sl-card>\n          <div slot=\"header\">Header Title</div>\n          This card has a header. You can put all sorts of things in it!\n        </sl-card>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should render the child content provided.', () => {\n      expect(el.innerText).to.contain('This card has a header. You can put all sorts of things in it!');\n    });\n\n    it('render the header content provided.', () => {\n      const header = el.querySelector<HTMLElement>('div[slot=header]')!;\n      expect(header.innerText).eq('Header Title');\n    });\n\n    it('accept \"header\" as an assigned child in the shadow root.', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=header]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should contain the class card--has-header.', () => {\n      const card = el.shadowRoot!.querySelector('.card')!;\n      expect(card.classList.value.trim()).to.eq('card card--has-header');\n    });\n  });\n\n  describe('when provided an element in the slot \"footer\" to render a footer', () => {\n    before(async () => {\n      el = await fixture<SlCard>(\n        html`<sl-card>\n          This card has a footer. You can put all sorts of things in it!\n\n          <div slot=\"footer\">Footer Content</div>\n        </sl-card>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should render the child content provided.', () => {\n      expect(el.innerText).to.contain('This card has a footer. You can put all sorts of things in it!');\n    });\n\n    it('render the footer content provided.', () => {\n      const footer = el.querySelector<HTMLElement>('div[slot=footer]')!;\n      expect(footer.innerText).eq('Footer Content');\n    });\n\n    it('accept \"footer\" as an assigned child in the shadow root.', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=footer]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should contain the class card--has-footer.', () => {\n      const card = el.shadowRoot!.querySelector('.card')!;\n      expect(card.classList.value.trim()).to.eq('card card--has-footer');\n    });\n  });\n\n  describe('when provided an element in the slot \"image\" to render a image', () => {\n    before(async () => {\n      el = await fixture<SlCard>(\n        html`<sl-card>\n          <img\n            slot=\"image\"\n            src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\"\n            alt=\"A kitten walks towards camera on top of pallet.\"\n          />\n          This is a kitten, but not just any kitten. This kitten likes walking along pallets.\n        </sl-card>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should render the child content provided.', () => {\n      expect(el.innerText).to.contain(\n        'This is a kitten, but not just any kitten. This kitten likes walking along pallets.'\n      );\n    });\n\n    it('accept \"image\" as an assigned child in the shadow root.', () => {\n      const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=image]')!;\n      const childNodes = slot.assignedNodes({ flatten: true });\n\n      expect(childNodes.length).to.eq(1);\n    });\n\n    it('should contain the class card--has-image.', () => {\n      const card = el.shadowRoot!.querySelector('.card')!;\n      expect(card.classList.value.trim()).to.eq('card card--has-image');\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/card/card.ts",
    "content": "import SlCard from './card.component.js';\n\nexport * from './card.component.js';\nexport default SlCard;\n\nSlCard.define('sl-card');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-card': SlCard;\n  }\n}\n"
  },
  {
    "path": "src/components/carousel/autoplay-controller.ts",
    "content": "import type { ReactiveController, ReactiveElement } from 'lit';\n\n/**\n * A controller that repeatedly calls the specified callback with the provided interval time.\n * The timer is automatically paused while the user is interacting with the component.\n */\nexport class AutoplayController implements ReactiveController {\n  private host: ReactiveElement;\n  private timerId = 0;\n  private tickCallback: () => void;\n  private activeInteractions = 0;\n\n  paused = false;\n  stopped = true;\n\n  constructor(host: ReactiveElement, tickCallback: () => void) {\n    host.addController(this);\n\n    this.host = host;\n    this.tickCallback = tickCallback;\n  }\n\n  hostConnected(): void {\n    this.host.addEventListener('mouseenter', this.pause);\n    this.host.addEventListener('mouseleave', this.resume);\n    this.host.addEventListener('focusin', this.pause);\n    this.host.addEventListener('focusout', this.resume);\n    this.host.addEventListener('touchstart', this.pause, { passive: true });\n    this.host.addEventListener('touchend', this.resume);\n  }\n\n  hostDisconnected(): void {\n    this.stop();\n\n    this.host.removeEventListener('mouseenter', this.pause);\n    this.host.removeEventListener('mouseleave', this.resume);\n    this.host.removeEventListener('focusin', this.pause);\n    this.host.removeEventListener('focusout', this.resume);\n    this.host.removeEventListener('touchstart', this.pause);\n    this.host.removeEventListener('touchend', this.resume);\n  }\n\n  start(interval: number) {\n    this.stop();\n\n    this.stopped = false;\n    this.timerId = window.setInterval(() => {\n      if (!this.paused) {\n        this.tickCallback();\n      }\n    }, interval);\n  }\n\n  stop() {\n    clearInterval(this.timerId);\n    this.stopped = true;\n    this.host.requestUpdate();\n  }\n\n  pause = () => {\n    if (!this.activeInteractions++) {\n      this.paused = true;\n      this.host.requestUpdate();\n    }\n  };\n\n  resume = () => {\n    if (!--this.activeInteractions) {\n      this.paused = false;\n      this.host.requestUpdate();\n    }\n  };\n}\n"
  },
  {
    "path": "src/components/carousel/carousel.component.ts",
    "content": "import '../../internal/scrollend-polyfill.js';\n\nimport { AutoplayController } from './autoplay-controller.js';\nimport { clamp } from '../../internal/math.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { eventOptions, property, query, state } from 'lit/decorators.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { map } from 'lit/directives/map.js';\nimport { prefersReducedMotion } from '../../internal/animate.js';\nimport { range } from 'lit/directives/range.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './carousel.styles.js';\nimport type { CSSResultGroup, PropertyValueMap } from 'lit';\nimport type SlCarouselItem from '../carousel-item/carousel-item.component.js';\n\n/**\n * @summary Carousels display an arbitrary number of content slides along a horizontal or vertical axis.\n *\n * @since 2.2\n * @status experimental\n *\n * @dependency sl-icon\n *\n * @event {{ index: number, slide: SlCarouselItem }} sl-slide-change - Emitted when the active slide changes.\n *\n * @slot - The carousel's main content, one or more `<sl-carousel-item>` elements.\n * @slot next-icon - Optional next icon to use instead of the default. Works best with `<sl-icon>`.\n * @slot previous-icon - Optional previous icon to use instead of the default. Works best with `<sl-icon>`.\n *\n * @csspart base - The carousel's internal wrapper.\n * @csspart scroll-container - The scroll container that wraps the slides.\n * @csspart pagination - The pagination indicators wrapper.\n * @csspart pagination-item - The pagination indicator.\n * @csspart pagination-item--active - Applied when the item is active.\n * @csspart navigation - The navigation wrapper.\n * @csspart navigation-button - The navigation button.\n * @csspart navigation-button--previous - Applied to the previous button.\n * @csspart navigation-button--next - Applied to the next button.\n *\n * @cssproperty --slide-gap - The space between each slide.\n * @cssproperty [--aspect-ratio=16/9] - The aspect ratio of each slide.\n * @cssproperty --scroll-hint - The amount of padding to apply to the scroll area, allowing adjacent slides to become\n *  partially visible as a scroll hint.\n */\nexport default class SlCarousel extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  /** When set, allows the user to navigate the carousel in the same direction indefinitely. */\n  @property({ type: Boolean, reflect: true }) loop = false;\n\n  /** When set, show the carousel's navigation. */\n  @property({ type: Boolean, reflect: true }) navigation = false;\n\n  /** When set, show the carousel's pagination indicators. */\n  @property({ type: Boolean, reflect: true }) pagination = false;\n\n  /** When set, the slides will scroll automatically when the user is not interacting with them.  */\n  @property({ type: Boolean, reflect: true }) autoplay = false;\n\n  /** Specifies the amount of time, in milliseconds, between each automatic scroll.  */\n  @property({ type: Number, attribute: 'autoplay-interval' }) autoplayInterval = 3000;\n\n  /** Specifies how many slides should be shown at a given time.  */\n  @property({ type: Number, attribute: 'slides-per-page' }) slidesPerPage = 1;\n\n  /**\n   * Specifies the number of slides the carousel will advance when scrolling, useful when specifying a `slides-per-page`\n   * greater than one. It can't be higher than `slides-per-page`.\n   */\n  @property({ type: Number, attribute: 'slides-per-move' }) slidesPerMove = 1;\n\n  /** Specifies the orientation in which the carousel will lay out.  */\n  @property() orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n  /** When set, it is possible to scroll through the slides by dragging them with the mouse. */\n  @property({ type: Boolean, reflect: true, attribute: 'mouse-dragging' }) mouseDragging = false;\n\n  @query('.carousel__slides') scrollContainer: HTMLElement;\n  @query('.carousel__pagination') paginationContainer: HTMLElement;\n\n  // The index of the active slide\n  @state() activeSlide = 0;\n\n  @state() scrolling = false;\n\n  @state() dragging = false;\n\n  private autoplayController = new AutoplayController(this, () => this.next());\n  private dragStartPosition: [number, number] = [-1, -1];\n  private readonly localize = new LocalizeController(this);\n  private mutationObserver: MutationObserver;\n  private pendingSlideChange = false;\n\n  connectedCallback(): void {\n    super.connectedCallback();\n    this.setAttribute('role', 'region');\n    this.setAttribute('aria-label', this.localize.term('carousel'));\n  }\n\n  disconnectedCallback(): void {\n    super.disconnectedCallback();\n    this.mutationObserver?.disconnect();\n  }\n\n  protected firstUpdated(): void {\n    this.initializeSlides();\n    this.mutationObserver = new MutationObserver(this.handleSlotChange);\n    this.mutationObserver.observe(this, {\n      childList: true,\n      subtree: true\n    });\n  }\n\n  protected willUpdate(changedProperties: PropertyValueMap<SlCarousel> | Map<PropertyKey, unknown>): void {\n    // Ensure the slidesPerMove is never higher than the slidesPerPage\n    if (changedProperties.has('slidesPerMove') || changedProperties.has('slidesPerPage')) {\n      this.slidesPerMove = Math.min(this.slidesPerMove, this.slidesPerPage);\n    }\n  }\n\n  private getPageCount() {\n    const slidesCount = this.getSlides().length;\n    const { slidesPerPage, slidesPerMove, loop } = this;\n\n    const pages = loop ? slidesCount / slidesPerMove : (slidesCount - slidesPerPage) / slidesPerMove + 1;\n\n    return Math.ceil(pages);\n  }\n\n  private getCurrentPage() {\n    return Math.ceil(this.activeSlide / this.slidesPerMove);\n  }\n\n  private canScrollNext(): boolean {\n    return this.loop || this.getCurrentPage() < this.getPageCount() - 1;\n  }\n\n  private canScrollPrev(): boolean {\n    return this.loop || this.getCurrentPage() > 0;\n  }\n\n  /** @internal Gets all carousel items. */\n  private getSlides({ excludeClones = true }: { excludeClones?: boolean } = {}) {\n    return [...this.children].filter(\n      (el: HTMLElement) => this.isCarouselItem(el) && (!excludeClones || !el.hasAttribute('data-clone'))\n    ) as SlCarouselItem[];\n  }\n\n  private handleClick(event: MouseEvent) {\n    if (this.dragging && this.dragStartPosition[0] > 0 && this.dragStartPosition[1] > 0) {\n      const deltaX = Math.abs(this.dragStartPosition[0] - event.clientX);\n      const deltaY = Math.abs(this.dragStartPosition[1] - event.clientY);\n      const delta = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n      // Prevents clicks on interactive elements while dragging if the click is within a small range. This prevents\n      // accidental drags from interfering with intentional clicks.\n      if (delta >= 10) {\n        event.preventDefault();\n      }\n    }\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {\n      const target = event.target as HTMLElement;\n      const isRtl = this.localize.dir() === 'rtl';\n      const isFocusInPagination = target.closest('[part~=\"pagination-item\"]') !== null;\n      const isNext =\n        event.key === 'ArrowDown' || (!isRtl && event.key === 'ArrowRight') || (isRtl && event.key === 'ArrowLeft');\n      const isPrevious =\n        event.key === 'ArrowUp' || (!isRtl && event.key === 'ArrowLeft') || (isRtl && event.key === 'ArrowRight');\n\n      event.preventDefault();\n\n      if (isPrevious) {\n        this.previous();\n      }\n\n      if (isNext) {\n        this.next();\n      }\n\n      if (event.key === 'Home') {\n        this.goToSlide(0);\n      }\n\n      if (event.key === 'End') {\n        this.goToSlide(this.getSlides().length - 1);\n      }\n\n      if (isFocusInPagination) {\n        this.updateComplete.then(() => {\n          const activePaginationItem = this.shadowRoot?.querySelector<HTMLButtonElement>(\n            '[part~=\"pagination-item--active\"]'\n          );\n\n          if (activePaginationItem) {\n            activePaginationItem.focus();\n          }\n        });\n      }\n    }\n  }\n\n  private handleMouseDragStart(event: PointerEvent) {\n    const canDrag = this.mouseDragging && event.button === 0;\n    if (canDrag) {\n      event.preventDefault();\n\n      document.addEventListener('pointermove', this.handleMouseDrag, { capture: true, passive: true });\n      document.addEventListener('pointerup', this.handleMouseDragEnd, { capture: true, once: true });\n    }\n  }\n\n  private handleMouseDrag = (event: PointerEvent) => {\n    if (!this.dragging) {\n      // Start dragging if it hasn't yet\n      this.scrollContainer.style.setProperty('scroll-snap-type', 'none');\n      this.dragging = true;\n      this.dragStartPosition = [event.clientX, event.clientY];\n    }\n\n    this.scrollContainer.scrollBy({\n      left: -event.movementX,\n      top: -event.movementY,\n      behavior: 'instant'\n    });\n  };\n\n  private handleMouseDragEnd = () => {\n    const scrollContainer = this.scrollContainer;\n\n    document.removeEventListener('pointermove', this.handleMouseDrag, { capture: true });\n\n    // get the current scroll position\n    const startLeft = scrollContainer.scrollLeft;\n    const startTop = scrollContainer.scrollTop;\n\n    // remove the scroll-snap-type property so that the browser will snap the slide to the correct position\n    scrollContainer.style.removeProperty('scroll-snap-type');\n\n    // fix(safari): forcing a style recalculation doesn't seem to immediately update the scroll\n    // position in Safari. Setting \"overflow\" to \"hidden\" should force this behavior.\n    scrollContainer.style.setProperty('overflow', 'hidden');\n\n    // get the final scroll position to the slide snapped by the browser\n    const finalLeft = scrollContainer.scrollLeft;\n    const finalTop = scrollContainer.scrollTop;\n\n    // restore the scroll position to the original one, so that it can be smoothly animated if needed\n    scrollContainer.style.removeProperty('overflow');\n    scrollContainer.style.setProperty('scroll-snap-type', 'none');\n    scrollContainer.scrollTo({ left: startLeft, top: startTop, behavior: 'instant' });\n\n    requestAnimationFrame(async () => {\n      if (startLeft !== finalLeft || startTop !== finalTop) {\n        scrollContainer.scrollTo({\n          left: finalLeft,\n          top: finalTop,\n          behavior: prefersReducedMotion() ? 'auto' : 'smooth'\n        });\n        await waitForEvent(scrollContainer, 'scrollend');\n      }\n\n      scrollContainer.style.removeProperty('scroll-snap-type');\n\n      this.dragging = false;\n      this.dragStartPosition = [-1, -1];\n      this.handleScrollEnd();\n    });\n  };\n\n  @eventOptions({ passive: true })\n  private handleScroll() {\n    this.scrolling = true;\n    if (!this.pendingSlideChange) {\n      this.synchronizeSlides();\n    }\n  }\n\n  /** @internal Synchronizes the slides with the IntersectionObserver API. */\n  private synchronizeSlides() {\n    const io = new IntersectionObserver(\n      entries => {\n        io.disconnect();\n\n        for (const entry of entries) {\n          const slide = entry.target;\n          slide.toggleAttribute('inert', !entry.isIntersecting);\n          slide.classList.toggle('--in-view', entry.isIntersecting);\n          slide.setAttribute('aria-hidden', entry.isIntersecting ? 'false' : 'true');\n        }\n\n        const firstIntersecting = entries.find(entry => entry.isIntersecting);\n        if (!firstIntersecting) {\n          return;\n        }\n\n        const slidesWithClones = this.getSlides({ excludeClones: false });\n        const slidesCount = this.getSlides().length;\n\n        // Update the current index based on the first visible slide\n        const slideIndex = slidesWithClones.indexOf(firstIntersecting.target as SlCarouselItem);\n        // Normalize the index to ignore clones\n        const normalizedIndex = this.loop ? slideIndex - this.slidesPerPage : slideIndex;\n\n        // Set the index to the closest \"snappable\" slide\n        this.activeSlide =\n          (Math.ceil(normalizedIndex / this.slidesPerMove) * this.slidesPerMove + slidesCount) % slidesCount;\n\n        if (!this.scrolling) {\n          if (this.loop && firstIntersecting.target.hasAttribute('data-clone')) {\n            const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone'));\n\n            // Scrolls to the original slide without animating, so the user won't notice that the position has changed\n            this.goToSlide(clonePosition, 'instant');\n          }\n        }\n      },\n      {\n        root: this.scrollContainer,\n        threshold: 0.6\n      }\n    );\n\n    this.getSlides({ excludeClones: false }).forEach(slide => {\n      io.observe(slide);\n    });\n  }\n\n  private handleScrollEnd() {\n    if (!this.scrolling || this.dragging) return;\n    this.scrolling = false;\n    this.pendingSlideChange = false;\n    this.synchronizeSlides();\n  }\n\n  private isCarouselItem(node: Node): node is SlCarouselItem {\n    return node instanceof Element && node.tagName.toLowerCase() === 'sl-carousel-item';\n  }\n\n  private handleSlotChange = (mutations: MutationRecord[]) => {\n    const needsInitialization = mutations.some(mutation =>\n      [...mutation.addedNodes, ...mutation.removedNodes].some(\n        (el: HTMLElement) => this.isCarouselItem(el) && !el.hasAttribute('data-clone')\n      )\n    );\n\n    // Reinitialize the carousel if a carousel item has been added or removed\n    if (needsInitialization) {\n      this.initializeSlides();\n    }\n\n    this.requestUpdate();\n  };\n\n  @watch('loop', { waitUntilFirstUpdate: true })\n  @watch('slidesPerPage', { waitUntilFirstUpdate: true })\n  initializeSlides() {\n    // Removes all the cloned elements from the carousel\n    this.getSlides({ excludeClones: false }).forEach((slide, index) => {\n      slide.classList.remove('--in-view');\n      slide.classList.remove('--is-active');\n      slide.setAttribute('role', 'group');\n      slide.setAttribute('aria-label', this.localize.term('slideNum', index + 1));\n\n      if (this.pagination) {\n        slide.setAttribute('id', `slide-${index + 1}`);\n        slide.setAttribute('role', 'tabpanel');\n        slide.removeAttribute('aria-label');\n        slide.setAttribute('aria-labelledby', `tab-${index + 1}`);\n      }\n\n      if (slide.hasAttribute('data-clone')) {\n        slide.remove();\n      }\n    });\n\n    this.updateSlidesSnap();\n\n    if (this.loop) {\n      // Creates clones to be placed before and after the original elements to simulate infinite scrolling\n      this.createClones();\n    }\n\n    // Because the DOM may be changed, restore the scroll position to the active slide\n    this.goToSlide(this.activeSlide, 'auto');\n\n    this.synchronizeSlides();\n  }\n\n  private createClones() {\n    const slides = this.getSlides();\n\n    const slidesPerPage = this.slidesPerPage;\n    const lastSlides = slides.slice(-slidesPerPage);\n    const firstSlides = slides.slice(0, slidesPerPage);\n\n    lastSlides.reverse().forEach((slide, i) => {\n      const clone = slide.cloneNode(true) as HTMLElement;\n      clone.setAttribute('data-clone', String(slides.length - i - 1));\n      this.prepend(clone);\n    });\n\n    firstSlides.forEach((slide, i) => {\n      const clone = slide.cloneNode(true) as HTMLElement;\n      clone.setAttribute('data-clone', String(i));\n      this.append(clone);\n    });\n  }\n\n  @watch('activeSlide')\n  handleSlideChange() {\n    const slides = this.getSlides();\n    slides.forEach((slide, i) => {\n      slide.classList.toggle('--is-active', i === this.activeSlide);\n    });\n\n    // Do not emit an event on first render\n    if (this.hasUpdated) {\n      this.emit('sl-slide-change', {\n        detail: {\n          index: this.activeSlide,\n          slide: slides[this.activeSlide]\n        }\n      });\n    }\n  }\n\n  @watch('slidesPerMove')\n  updateSlidesSnap() {\n    const slides = this.getSlides();\n\n    const slidesPerMove = this.slidesPerMove;\n    slides.forEach((slide, i) => {\n      const shouldSnap = (i + slidesPerMove) % slidesPerMove === 0;\n      if (shouldSnap) {\n        slide.style.removeProperty('scroll-snap-align');\n      } else {\n        slide.style.setProperty('scroll-snap-align', 'none');\n      }\n    });\n  }\n\n  @watch('autoplay')\n  handleAutoplayChange() {\n    this.autoplayController.stop();\n    if (this.autoplay) {\n      this.autoplayController.start(this.autoplayInterval);\n    }\n  }\n\n  /**\n   * Move the carousel backward by `slides-per-move` slides.\n   *\n   * @param behavior - The behavior used for scrolling.\n   */\n  previous(behavior: ScrollBehavior = 'smooth') {\n    this.goToSlide(this.activeSlide - this.slidesPerMove, behavior);\n  }\n\n  /**\n   * Move the carousel forward by `slides-per-move` slides.\n   *\n   * @param behavior - The behavior used for scrolling.\n   */\n  next(behavior: ScrollBehavior = 'smooth') {\n    this.goToSlide(this.activeSlide + this.slidesPerMove, behavior);\n  }\n\n  /**\n   * Scrolls the carousel to the slide specified by `index`.\n   *\n   * @param index - The slide index.\n   * @param behavior - The behavior used for scrolling.\n   */\n  goToSlide(index: number, behavior: ScrollBehavior = 'smooth') {\n    const { slidesPerPage, loop } = this;\n\n    const slides = this.getSlides();\n    const slidesWithClones = this.getSlides({ excludeClones: false });\n\n    // No need to do anything in case there are no items in the carousel\n    if (!slides.length) {\n      return;\n    }\n\n    // Sets the next index without taking into account clones, if any.\n    const newActiveSlide = loop\n      ? (index + slides.length) % slides.length\n      : clamp(index, 0, slides.length - slidesPerPage);\n    this.activeSlide = newActiveSlide;\n\n    const isRtl = this.localize.dir() === 'rtl';\n\n    // Get the index of the next slide. For looping carousel it adds `slidesPerPage`\n    // to normalize the starting index in order to ignore the first nth clones.\n    // For RTL it needs to scroll to the last slide of the page.\n    const nextSlideIndex = clamp(\n      index + (loop ? slidesPerPage : 0) + (isRtl ? slidesPerPage - 1 : 0),\n      0,\n      slidesWithClones.length - 1\n    );\n\n    const nextSlide = slidesWithClones[nextSlideIndex];\n\n    this.scrollToSlide(nextSlide, prefersReducedMotion() ? 'auto' : behavior);\n  }\n\n  private scrollToSlide(slide: HTMLElement, behavior: ScrollBehavior = 'smooth') {\n    // Since the geometry doesn't happen until rAF, we don't know if we'll be scrolling or not...\n    // It's best to assume that we will and cleanup in the else case below if we didn't need to\n    this.pendingSlideChange = true;\n    window.requestAnimationFrame(() => {\n      // This can happen if goToSlide is called before the scroll container is rendered\n      // We will have correctly set the activeSlide in goToSlide which will get picked up when initializeSlides is called.\n      if (!this.scrollContainer) {\n        return;\n      }\n\n      const scrollContainer = this.scrollContainer;\n      const scrollContainerRect = scrollContainer.getBoundingClientRect();\n      const nextSlideRect = slide.getBoundingClientRect();\n\n      const nextLeft = nextSlideRect.left - scrollContainerRect.left;\n      const nextTop = nextSlideRect.top - scrollContainerRect.top;\n\n      if (nextLeft || nextTop) {\n        // This is here just in case someone set it back to false\n        // between rAF being requested and the callback actually running\n        this.pendingSlideChange = true;\n        scrollContainer.scrollTo({\n          left: nextLeft + scrollContainer.scrollLeft,\n          top: nextTop + scrollContainer.scrollTop,\n          behavior\n        });\n      } else {\n        this.pendingSlideChange = false;\n      }\n    });\n  }\n\n  render() {\n    const { slidesPerMove, scrolling } = this;\n    const pagesCount = this.getPageCount();\n    const currentPage = this.getCurrentPage();\n    const prevEnabled = this.canScrollPrev();\n    const nextEnabled = this.canScrollNext();\n    const isLtr = this.localize.dir() === 'ltr';\n\n    return html`\n      <div part=\"base\" class=\"carousel\">\n        <div\n          id=\"scroll-container\"\n          part=\"scroll-container\"\n          class=\"${classMap({\n            carousel__slides: true,\n            'carousel__slides--horizontal': this.orientation === 'horizontal',\n            'carousel__slides--vertical': this.orientation === 'vertical',\n            'carousel__slides--dragging': this.dragging\n          })}\"\n          style=\"--slides-per-page: ${this.slidesPerPage};\"\n          aria-busy=\"${scrolling ? 'true' : 'false'}\"\n          aria-atomic=\"true\"\n          tabindex=\"0\"\n          @keydown=${this.handleKeyDown}\n          @mousedown=\"${this.handleMouseDragStart}\"\n          @scroll=\"${this.handleScroll}\"\n          @scrollend=${this.handleScrollEnd}\n          @click=${this.handleClick}\n        >\n          <slot></slot>\n        </div>\n\n        ${this.navigation\n          ? html`\n              <div part=\"navigation\" class=\"carousel__navigation\">\n                <button\n                  part=\"navigation-button navigation-button--previous\"\n                  class=\"${classMap({\n                    'carousel__navigation-button': true,\n                    'carousel__navigation-button--previous': true,\n                    'carousel__navigation-button--disabled': !prevEnabled\n                  })}\"\n                  aria-label=\"${this.localize.term('previousSlide')}\"\n                  aria-controls=\"scroll-container\"\n                  aria-disabled=\"${prevEnabled ? 'false' : 'true'}\"\n                  @click=${prevEnabled ? () => this.previous() : null}\n                >\n                  <slot name=\"previous-icon\">\n                    <sl-icon library=\"system\" name=\"${isLtr ? 'chevron-left' : 'chevron-right'}\"></sl-icon>\n                  </slot>\n                </button>\n\n                <button\n                  part=\"navigation-button navigation-button--next\"\n                  class=${classMap({\n                    'carousel__navigation-button': true,\n                    'carousel__navigation-button--next': true,\n                    'carousel__navigation-button--disabled': !nextEnabled\n                  })}\n                  aria-label=\"${this.localize.term('nextSlide')}\"\n                  aria-controls=\"scroll-container\"\n                  aria-disabled=\"${nextEnabled ? 'false' : 'true'}\"\n                  @click=${nextEnabled ? () => this.next() : null}\n                >\n                  <slot name=\"next-icon\">\n                    <sl-icon library=\"system\" name=\"${isLtr ? 'chevron-right' : 'chevron-left'}\"></sl-icon>\n                  </slot>\n                </button>\n              </div>\n            `\n          : ''}\n        ${this.pagination\n          ? html`\n              <div part=\"pagination\" role=\"tablist\" class=\"carousel__pagination\">\n                ${map(range(pagesCount), index => {\n                  const isActive = index === currentPage;\n                  return html`\n                    <button\n                      part=\"pagination-item ${isActive ? 'pagination-item--active' : ''}\"\n                      class=\"${classMap({\n                        'carousel__pagination-item': true,\n                        'carousel__pagination-item--active': isActive\n                      })}\"\n                      role=\"tab\"\n                      id=\"tab-${index + 1}\"\n                      aria-controls=\"slide-${index + 1}\"\n                      aria-selected=\"${isActive ? 'true' : 'false'}\"\n                      aria-label=\"${isActive\n                        ? this.localize.term('slideNum', index + 1)\n                        : this.localize.term('goToSlide', index + 1, pagesCount)}\"\n                      tabindex=${isActive ? '0' : '-1'}\n                      @click=${() => this.goToSlide(index * slidesPerMove)}\n                      @keydown=${this.handleKeyDown}\n                    ></button>\n                  `;\n                })}\n              </div>\n            `\n          : ''}\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/carousel/carousel.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --slide-gap: var(--sl-spacing-medium, 1rem);\n    --aspect-ratio: 16 / 9;\n    --scroll-hint: 0px;\n\n    display: flex;\n  }\n\n  .carousel {\n    display: grid;\n    grid-template-columns: min-content 1fr min-content;\n    grid-template-rows: 1fr min-content;\n    grid-template-areas:\n      '. slides .'\n      '. pagination .';\n    gap: var(--sl-spacing-medium);\n    align-items: center;\n    min-height: 100%;\n    min-width: 100%;\n    position: relative;\n  }\n\n  .carousel__pagination {\n    grid-area: pagination;\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: center;\n    gap: var(--sl-spacing-small);\n  }\n\n  .carousel__slides {\n    grid-area: slides;\n\n    display: grid;\n    height: 100%;\n    width: 100%;\n    align-items: center;\n    justify-items: center;\n    overflow: auto;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n    aspect-ratio: calc(var(--aspect-ratio) * var(--slides-per-page));\n    border-radius: var(--sl-border-radius-small);\n\n    --slide-size: calc((100% - (var(--slides-per-page) - 1) * var(--slide-gap)) / var(--slides-per-page));\n  }\n\n  @media (prefers-reduced-motion) {\n    :where(.carousel__slides) {\n      scroll-behavior: auto;\n    }\n  }\n\n  .carousel__slides--horizontal {\n    grid-auto-flow: column;\n    grid-auto-columns: var(--slide-size);\n    grid-auto-rows: 100%;\n    column-gap: var(--slide-gap);\n    scroll-snap-type: x mandatory;\n    scroll-padding-inline: var(--scroll-hint);\n    padding-inline: var(--scroll-hint);\n    overflow-y: hidden;\n  }\n\n  .carousel__slides--vertical {\n    grid-auto-flow: row;\n    grid-auto-columns: 100%;\n    grid-auto-rows: var(--slide-size);\n    row-gap: var(--slide-gap);\n    scroll-snap-type: y mandatory;\n    scroll-padding-block: var(--scroll-hint);\n    padding-block: var(--scroll-hint);\n    overflow-x: hidden;\n  }\n\n  .carousel__slides--dragging {\n  }\n\n  :host([vertical]) ::slotted(sl-carousel-item) {\n    height: 100%;\n  }\n\n  .carousel__slides::-webkit-scrollbar {\n    display: none;\n  }\n\n  .carousel__navigation {\n    grid-area: navigation;\n    display: contents;\n    font-size: var(--sl-font-size-x-large);\n  }\n\n  .carousel__navigation-button {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    background: none;\n    border: none;\n    border-radius: var(--sl-border-radius-small);\n    font-size: inherit;\n    color: var(--sl-color-neutral-600);\n    padding: var(--sl-spacing-x-small);\n    cursor: pointer;\n    transition: var(--sl-transition-medium) color;\n    appearance: none;\n  }\n\n  .carousel__navigation-button--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .carousel__navigation-button--disabled::part(base) {\n    pointer-events: none;\n  }\n\n  .carousel__navigation-button--previous {\n    grid-column: 1;\n    grid-row: 1;\n  }\n\n  .carousel__navigation-button--next {\n    grid-column: 3;\n    grid-row: 1;\n  }\n\n  .carousel__pagination-item {\n    display: block;\n    cursor: pointer;\n    background: none;\n    border: 0;\n    border-radius: var(--sl-border-radius-circle);\n    width: var(--sl-spacing-small);\n    height: var(--sl-spacing-small);\n    background-color: var(--sl-color-neutral-300);\n    padding: 0;\n    margin: 0;\n  }\n\n  .carousel__pagination-item--active {\n    background-color: var(--sl-color-neutral-700);\n    transform: scale(1.2);\n  }\n\n  /* Focus styles */\n  .carousel__slides:focus-visible,\n  .carousel__navigation-button:focus-visible,\n  .carousel__pagination-item:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n`;\n"
  },
  {
    "path": "src/components/carousel/carousel.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, nextFrame, oneEvent, waitUntil } from '@open-wc/testing';\nimport { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';\nimport { map } from 'lit/directives/map.js';\nimport { range } from 'lit/directives/range.js';\nimport { resetMouse } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type { SinonStub } from 'sinon';\nimport type SlCarousel from './carousel.js';\n\ndescribe('<sl-carousel>', () => {\n  const sandbox = sinon.createSandbox();\n  const ioCallbacks = new Map<IntersectionObserver, SinonStub>();\n  const intersectionObserverCallbacks = () => {\n    const callbacks = [...ioCallbacks.values()];\n    return waitUntil(() => callbacks.every(callback => callback.called));\n  };\n  const OriginalIntersectionObserver = globalThis.IntersectionObserver;\n\n  beforeEach(() => {\n    globalThis.IntersectionObserver = class IntersectionObserverMock extends OriginalIntersectionObserver {\n      constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {\n        const stubCallback = sandbox.stub().callsFake(callback);\n\n        super(stubCallback, options);\n\n        ioCallbacks.set(this, stubCallback);\n      }\n    };\n  });\n\n  afterEach(async () => {\n    await resetMouse();\n    sandbox.restore();\n    globalThis.IntersectionObserver = OriginalIntersectionObserver;\n    ioCallbacks.clear();\n  });\n\n  it('should render a carousel with default configuration', async () => {\n    // Arrange\n    const el = await fixture(html`\n      <sl-carousel>\n        <sl-carousel-item>Node 1</sl-carousel-item>\n        <sl-carousel-item>Node 2</sl-carousel-item>\n        <sl-carousel-item>Node 3</sl-carousel-item>\n      </sl-carousel>\n    `);\n\n    // Assert\n    expect(el).to.exist;\n    expect(el).to.have.attribute('role', 'region');\n    expect(el).to.have.attribute('aria-label', 'Carousel');\n    expect(el.shadowRoot!.querySelector('.carousel__navigation')).not.to.exist;\n    expect(el.shadowRoot!.querySelector('.carousel__pagination')).not.to.exist;\n  });\n\n  describe('when `autoplay` attribute is provided', () => {\n    let clock: sinon.SinonFakeTimers;\n\n    beforeEach(() => {\n      clock = sandbox.useFakeTimers({\n        now: new Date()\n      });\n    });\n\n    // TODO - this test is hanging the test runner, but autoplay was verified manually to work\n    it.skip('should scroll forwards every `autoplay-interval` milliseconds', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel autoplay autoplay-interval=\"10\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n      sandbox.stub(el, 'next');\n\n      await el.updateComplete;\n\n      // Act\n      clock.next();\n      clock.next();\n\n      // Assert\n      expect(el.next).to.have.been.calledTwice;\n    });\n\n    it('should pause the autoplay while the user is interacting', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel autoplay autoplay-interval=\"10\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n      sandbox.stub(el, 'next');\n\n      await el.updateComplete;\n\n      // Act\n      el.dispatchEvent(new Event('mouseenter'));\n      await el.updateComplete;\n      clock.next();\n      clock.next();\n\n      // Assert\n      expect(el.next).not.to.have.been.called;\n    });\n\n    it('should not resume if the user is still interacting', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel autoplay autoplay-interval=\"10\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n      sandbox.stub(el, 'next');\n\n      await el.updateComplete;\n\n      // Act\n      el.dispatchEvent(new Event('mouseenter'));\n      el.dispatchEvent(new Event('focusin'));\n      await el.updateComplete;\n\n      el.dispatchEvent(new Event('mouseleave'));\n      await el.updateComplete;\n\n      clock.next();\n      clock.next();\n\n      // Assert\n      expect(el.next).not.to.have.been.called;\n    });\n  });\n\n  describe('when `loop` attribute is provided', () => {\n    it('should create clones of the first and last slides', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel loop>\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Act\n      await el.updateComplete;\n\n      // Assert\n      expect(el.firstElementChild).to.have.attribute('data-clone', '2');\n      expect(el.lastElementChild).to.have.attribute('data-clone', '0');\n    });\n\n    describe('and `slides-per-page` is provided', () => {\n      it('should create multiple clones', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel loop slides-per-page=\"2\">\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n\n        // Act\n        await el.updateComplete;\n        const clones = [...el.children].filter(child => child.hasAttribute('data-clone'));\n\n        // Assert\n        expect(clones).to.have.lengthOf(4);\n      });\n    });\n  });\n\n  describe('when `pagination` attribute is provided', () => {\n    it('should render pagination controls', async () => {\n      // Arrange\n      const el = await fixture(html`\n        <sl-carousel pagination>\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Assert\n      expect(el).to.exist;\n      expect(el.shadowRoot!.querySelector('.carousel__navigation')).not.to.exist;\n      expect(el.shadowRoot!.querySelector('.carousel__pagination')).to.exist;\n    });\n\n    describe('and user clicks on a pagination button', () => {\n      it('should scroll the carousel to the nth slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel pagination>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n        sandbox.stub(el, 'goToSlide');\n        await el.updateComplete;\n\n        // Act\n        const paginationItem = el.shadowRoot!.querySelectorAll('.carousel__pagination-item')[2] as HTMLElement;\n        await clickOnElement(paginationItem);\n\n        expect(el.goToSlide).to.have.been.calledWith(2);\n      });\n    });\n  });\n\n  describe('when `navigation` attribute is provided', () => {\n    it('should render navigation controls', async () => {\n      // Arrange\n      const el = await fixture(html`\n        <sl-carousel navigation>\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Assert\n      expect(el).to.exist;\n      expect(el.shadowRoot!.querySelector('.carousel__navigation')).to.exist;\n      expect(el.shadowRoot!.querySelector('.carousel__pagination')).not.to.exist;\n    });\n  });\n\n  describe('when `slides-per-page` attribute is provided', () => {\n    it('should show multiple slides at a given time', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel slides-per-page=\"2\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Act\n      await el.updateComplete;\n\n      // Assert\n      expect(el.scrollContainer.style.getPropertyValue('--slides-per-page').trim()).to.be.equal('2');\n    });\n\n    [\n      [7, 2, 1, false, 6],\n      [5, 3, 3, false, 2],\n      [10, 2, 2, false, 5],\n      [7, 2, 1, true, 7],\n      [5, 3, 3, true, 2],\n      [10, 2, 2, true, 5]\n    ].forEach(([slides, slidesPerPage, slidesPerMove, loop, expected]: [number, number, number, boolean, number]) => {\n      it(`should display ${expected} pages for ${slides} slides grouped by ${slidesPerPage} and scrolled by ${slidesPerMove}${\n        loop ? ' (loop)' : ''\n      }`, async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel\n            pagination\n            navigation\n            slides-per-page=\"${slidesPerPage}\"\n            slides-per-move=\"${slidesPerMove}\"\n            ?loop=${loop}\n          >\n            ${map(range(slides), i => html`<sl-carousel-item>${i}</sl-carousel-item>`)}\n          </sl-carousel>\n        `);\n\n        // Assert\n        const paginationItems = el.shadowRoot!.querySelectorAll('.carousel__pagination-item');\n        expect(paginationItems.length).to.equal(expected);\n      });\n    });\n  });\n\n  describe('when `slides-per-move` attribute is provided', () => {\n    it('should set the granularity of snapping', async () => {\n      // Arrange\n      const expectedSnapGranularity = 2;\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel slides-per-page=\"${expectedSnapGranularity}\" slides-per-move=\"${expectedSnapGranularity}\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n          <sl-carousel-item>Node 4</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Act\n      await el.updateComplete;\n\n      // Assert\n      for (let i = 0; i < el.children.length; i++) {\n        const child = el.children[i] as HTMLElement;\n\n        if (i % expectedSnapGranularity === 0) {\n          expect(child.style.getPropertyValue('scroll-snap-align')).to.be.equal('');\n        } else {\n          expect(child.style.getPropertyValue('scroll-snap-align')).to.be.equal('none');\n        }\n      }\n    });\n\n    it('should be possible to move by the given number of slides at a time', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel navigation slides-per-move=\"2\" slides-per-page=\"2\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item class=\"expected\">Node 3</sl-carousel-item>\n          <sl-carousel-item class=\"expected\">Node 4</sl-carousel-item>\n          <sl-carousel-item>Node 5</sl-carousel-item>\n          <sl-carousel-item>Node 6</sl-carousel-item>\n        </sl-carousel>\n      `);\n      const expectedSlides = el.querySelectorAll('.expected')!;\n      const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;\n\n      // Act\n      await clickOnElement(nextButton);\n\n      await oneEvent(el.scrollContainer, 'scrollend');\n      await intersectionObserverCallbacks();\n      await el.updateComplete;\n\n      // Assert\n      for (const expectedSlide of expectedSlides) {\n        expect(expectedSlide).to.have.class('--in-view');\n        expect(expectedSlide).to.be.visible;\n      }\n    });\n\n    it('should be possible to move by a number that is less than the displayed number', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel navigation slides-per-move=\"1\" slides-per-page=\"2\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n          <sl-carousel-item>Node 4</sl-carousel-item>\n          <sl-carousel-item class=\"expected\">Node 5</sl-carousel-item>\n          <sl-carousel-item class=\"expected\">Node 6</sl-carousel-item>\n        </sl-carousel>\n      `);\n      const expectedSlides = el.querySelectorAll('.expected')!;\n      const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;\n\n      // Act\n      await clickOnElement(nextButton);\n      await aTimeout(50);\n      await clickOnElement(nextButton);\n      await aTimeout(50);\n      await clickOnElement(nextButton);\n      await aTimeout(50);\n      await clickOnElement(nextButton);\n      await aTimeout(50);\n      await clickOnElement(nextButton);\n      await aTimeout(50);\n      await clickOnElement(nextButton);\n\n      await oneEvent(el.scrollContainer, 'scrollend');\n      await intersectionObserverCallbacks();\n      await el.updateComplete;\n\n      // Assert\n      for (const expectedSlide of expectedSlides) {\n        expect(expectedSlide).to.have.class('--in-view');\n        expect(expectedSlide).to.be.visible;\n      }\n    });\n\n    it('should not be possible to move by a number that is greater than the displayed number', async () => {\n      // Arrange\n      const expectedSlidesPerMove = 2;\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel slides-per-page=\"${expectedSlidesPerMove}\">\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n          <sl-carousel-item>Node 4</sl-carousel-item>\n          <sl-carousel-item>Node 5</sl-carousel-item>\n          <sl-carousel-item>Node 6</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Act\n      el.slidesPerMove = 3;\n      await el.updateComplete;\n\n      // Assert\n      expect(el.slidesPerMove).to.be.equal(expectedSlidesPerMove);\n    });\n  });\n\n  describe('when `orientation` attribute is provided', () => {\n    describe('and value is `vertical`', () => {\n      it('should make the scrollable along the y-axis', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel orientation=\"vertical\" style=\"height: 100px\">\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n          </sl-carousel>\n        `);\n\n        // Act\n        await el.updateComplete;\n\n        // Assert\n        expect(el.scrollContainer.scrollWidth).to.be.equal(el.scrollContainer.clientWidth);\n        expect(el.scrollContainer.scrollHeight).to.be.greaterThan(el.scrollContainer.clientHeight);\n      });\n    });\n\n    describe('and value is `horizontal`', () => {\n      it('should make the scrollable along the x-axis', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel orientation=\"horizontal\" style=\"height: 100px\">\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n          </sl-carousel>\n        `);\n\n        // Act\n        await el.updateComplete;\n\n        // Assert\n        expect(el.scrollContainer.scrollWidth).to.be.greaterThan(el.scrollContainer.clientWidth);\n        expect(el.scrollContainer.scrollHeight).to.be.equal(el.scrollContainer.clientHeight);\n      });\n    });\n  });\n\n  describe('when `mouse-dragging` attribute is provided', () => {\n    // TODO(alenaksu): skipping because failing in webkit, PointerEvent.movementX and PointerEvent.movementY seem to return incorrect values\n    it.skip('should be possible to drag the carousel using the mouse', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel mouse-dragging>\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n\n      // Act\n      await dragElement(el, -Math.round(el.offsetWidth * 0.75));\n      await oneEvent(el.scrollContainer, 'scrollend');\n      await dragElement(el, -Math.round(el.offsetWidth * 0.75));\n      await oneEvent(el.scrollContainer, 'scrollend');\n\n      await el.updateComplete;\n\n      // Assert\n      expect(el.activeSlide).to.be.equal(2);\n    });\n\n    it('should be possible to interact with clickable elements', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel mouse-dragging>\n          <sl-carousel-item><button>click me</button></sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n      const button = el.querySelector('button')!;\n\n      const clickSpy = sinon.spy();\n      button.addEventListener('click', clickSpy);\n\n      // Act\n      await moveMouseOnElement(button);\n      await clickOnElement(button);\n\n      // Assert\n      expect(clickSpy).to.have.been.called;\n    });\n  });\n\n  describe('Navigation controls', () => {\n    describe('when the user clicks the next button', () => {\n      it('should scroll to the next slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel navigation>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n        const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;\n        sandbox.stub(el, 'next');\n\n        await el.updateComplete;\n\n        // Act\n        await clickOnElement(nextButton);\n        await el.updateComplete;\n\n        // Assert\n        expect(el.next).to.have.been.calledOnce;\n      });\n\n      describe('and carousel is positioned on the last slide', () => {\n        it('should not scroll', async () => {\n          // Arrange\n          const el = await fixture<SlCarousel>(html`\n            <sl-carousel navigation>\n              <sl-carousel-item>Node 1</sl-carousel-item>\n              <sl-carousel-item>Node 2</sl-carousel-item>\n              <sl-carousel-item>Node 3</sl-carousel-item>\n            </sl-carousel>\n          `);\n          const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;\n          sandbox.stub(el, 'next');\n\n          el.goToSlide(2, 'auto');\n          await oneEvent(el.scrollContainer, 'scrollend');\n          await intersectionObserverCallbacks();\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(nextButton);\n          await el.updateComplete;\n\n          // Assert\n          expect(nextButton).to.have.attribute('aria-disabled', 'true');\n          expect(el.next).not.to.have.been.called;\n        });\n\n        describe('and `loop` attribute is provided', () => {\n          it('should scroll to the first slide', async () => {\n            // Arrange\n            const el = await fixture<SlCarousel>(html`\n              <sl-carousel navigation loop>\n                <sl-carousel-item>Node 1</sl-carousel-item>\n                <sl-carousel-item>Node 2</sl-carousel-item>\n                <sl-carousel-item>Node 3</sl-carousel-item>\n              </sl-carousel>\n            `);\n            const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;\n\n            el.goToSlide(2, 'auto');\n            await oneEvent(el.scrollContainer, 'scrollend');\n            await el.updateComplete;\n\n            // Act\n            await clickOnElement(nextButton);\n\n            // wait first scroll to clone\n            await oneEvent(el.scrollContainer, 'scrollend');\n            // wait scroll to actual item\n            await oneEvent(el.scrollContainer, 'scrollend');\n\n            await intersectionObserverCallbacks();\n            await el.updateComplete;\n\n            // Assert\n            expect(nextButton).to.have.attribute('aria-disabled', 'false');\n            expect(el.activeSlide).to.be.equal(0);\n          });\n        });\n      });\n    });\n\n    describe('and clicks the previous button', () => {\n      it('should scroll to the previous slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel navigation>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n\n        // Go to the second slide so that the previous button will be enabled\n        el.goToSlide(1, 'auto');\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await el.updateComplete;\n\n        const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;\n        sandbox.stub(el, 'previous');\n\n        await el.updateComplete;\n\n        // Act\n        await clickOnElement(previousButton);\n        await el.updateComplete;\n\n        // Assert\n        expect(el.previous).to.have.been.calledOnce;\n      });\n\n      describe('and carousel is positioned on the first slide', () => {\n        it('should not scroll', async () => {\n          // Arrange\n          const el = await fixture<SlCarousel>(html`\n            <sl-carousel navigation>\n              <sl-carousel-item>Node 1</sl-carousel-item>\n              <sl-carousel-item>Node 2</sl-carousel-item>\n              <sl-carousel-item>Node 3</sl-carousel-item>\n            </sl-carousel>\n          `);\n\n          const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;\n          sandbox.stub(el, 'previous');\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(previousButton);\n          await el.updateComplete;\n\n          // Assert\n          expect(previousButton).to.have.attribute('aria-disabled', 'true');\n          expect(el.previous).not.to.have.been.called;\n        });\n\n        describe('and `loop` attribute is provided', () => {\n          it('should scroll to the last slide', async () => {\n            // Arrange\n            const el = await fixture<SlCarousel>(html`\n              <sl-carousel navigation loop>\n                <sl-carousel-item>Node 1</sl-carousel-item>\n                <sl-carousel-item>Node 2</sl-carousel-item>\n                <sl-carousel-item>Node 3</sl-carousel-item>\n              </sl-carousel>\n            `);\n\n            const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;\n            await el.updateComplete;\n\n            // Act\n            await clickOnElement(previousButton);\n\n            // wait first scroll to clone\n            await oneEvent(el.scrollContainer, 'scrollend');\n            // wait scroll to actual item\n            await oneEvent(el.scrollContainer, 'scrollend');\n\n            await intersectionObserverCallbacks();\n\n            // Assert\n            expect(previousButton).to.have.attribute('aria-disabled', 'false');\n            expect(el.activeSlide).to.be.equal(2);\n          });\n        });\n      });\n    });\n  });\n\n  describe('API', () => {\n    describe('#next', () => {\n      it('should scroll the carousel to the next slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n        sandbox.spy(el, 'goToSlide');\n        const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(2)')!;\n\n        // Act\n        el.next();\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await el.updateComplete;\n\n        const containerRect = el.scrollContainer.getBoundingClientRect();\n        const itemRect = expectedCarouselItem.getBoundingClientRect();\n\n        // Assert\n        expect(el.goToSlide).to.have.been.calledWith(1);\n        expect(itemRect.top).to.be.equal(containerRect.top);\n        expect(itemRect.left).to.be.equal(containerRect.left);\n      });\n    });\n\n    describe('#previous', () => {\n      it('should scroll the carousel to the previous slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n        const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(1)')!;\n\n        el.goToSlide(1);\n\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await intersectionObserverCallbacks();\n        await nextFrame();\n\n        sandbox.spy(el, 'goToSlide');\n\n        // Act\n        el.previous();\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await intersectionObserverCallbacks();\n\n        const containerRect = el.scrollContainer.getBoundingClientRect();\n        const itemRect = expectedCarouselItem.getBoundingClientRect();\n\n        // Assert\n        expect(el.goToSlide).to.have.been.calledWith(0);\n        expect(itemRect.top).to.be.equal(containerRect.top);\n        expect(itemRect.left).to.be.equal(containerRect.left);\n      });\n    });\n\n    describe('#goToSlide', () => {\n      it('should scroll the carousel to the nth slide', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n        await el.updateComplete;\n\n        // Act\n        el.goToSlide(2);\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await intersectionObserverCallbacks();\n        await el.updateComplete;\n\n        // Assert\n        expect(el.activeSlide).to.be.equal(2);\n      });\n    });\n  });\n\n  describe('Accessibility', () => {\n    it('should pass accessibility tests', async () => {\n      // Arrange\n      const el = await fixture<SlCarousel>(html`\n        <sl-carousel navigation pagination>\n          <sl-carousel-item>Node 1</sl-carousel-item>\n          <sl-carousel-item>Node 2</sl-carousel-item>\n          <sl-carousel-item>Node 3</sl-carousel-item>\n        </sl-carousel>\n      `);\n      const pagination = el.shadowRoot!.querySelector('.carousel__pagination')!;\n      const navigation = el.shadowRoot!.querySelector('.carousel__navigation')!;\n      await el.updateComplete;\n\n      // Assert\n      expect(el.scrollContainer).to.have.attribute('aria-busy', 'false');\n      expect(el.scrollContainer).to.have.attribute('aria-atomic', 'true');\n\n      expect(pagination).to.have.attribute('role', 'tablist');\n      let paginationItemIndex = 0;\n      for (const paginationItem of pagination.querySelectorAll('.carousel__pagination-item')) {\n        expect(paginationItem).to.have.attribute('id', `tab-${paginationItemIndex + 1}`);\n        expect(paginationItem).to.have.attribute('role', 'tab');\n        expect(paginationItem).to.have.attribute('aria-controls', `slide-${paginationItemIndex + 1}`);\n        expect(paginationItem).to.have.attribute('aria-selected');\n        expect(paginationItem).to.have.attribute('aria-label');\n        paginationItemIndex++;\n      }\n\n      for (const navigationItem of navigation.querySelectorAll('.carousel__navigation-item')) {\n        expect(navigationItem).to.have.attribute('aria-controls', el.scrollContainer.id);\n        expect(navigationItem).to.have.attribute('aria-disabled');\n        expect(navigationItem).to.have.attribute('aria-label');\n      }\n\n      await expect(el).to.be.accessible({ ignoredRules: ['aria-valid-attr-value'] });\n    });\n\n    describe('when scrolling', () => {\n      it('should update aria-busy attribute', async () => {\n        // Arrange\n        const el = await fixture<SlCarousel>(html`\n          <sl-carousel autoplay>\n            <sl-carousel-item>Node 1</sl-carousel-item>\n            <sl-carousel-item>Node 2</sl-carousel-item>\n            <sl-carousel-item>Node 3</sl-carousel-item>\n          </sl-carousel>\n        `);\n\n        await el.updateComplete;\n\n        // Act\n        el.goToSlide(2, 'smooth');\n        await oneEvent(el.scrollContainer, 'scroll');\n        await el.updateComplete;\n\n        // Assert\n        expect(el.scrollContainer).to.have.attribute('aria-busy', 'true');\n\n        await oneEvent(el.scrollContainer, 'scrollend');\n        await el.updateComplete;\n        expect(el.scrollContainer).to.have.attribute('aria-busy', 'false');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/carousel/carousel.ts",
    "content": "import SlCarousel from './carousel.component.js';\n\nexport * from './carousel.component.js';\nexport default SlCarousel;\n\nSlCarousel.define('sl-carousel');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-carousel': SlCarousel;\n  }\n}\n"
  },
  {
    "path": "src/components/carousel-item/carousel-item.component.ts",
    "content": "import { html } from 'lit';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './carousel-item.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary A carousel item represent a slide within a [carousel](/components/carousel).\n *\n * @since 2.0\n * @status experimental\n *\n * @slot - The carousel item's content..\n *\n * @cssproperty --aspect-ratio - The slide's aspect ratio. Inherited from the carousel by default.\n *\n */\nexport default class SlCarouselItem extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  connectedCallback() {\n    super.connectedCallback();\n  }\n\n  render() {\n    return html` <slot></slot> `;\n  }\n}\n"
  },
  {
    "path": "src/components/carousel-item/carousel-item.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --aspect-ratio: inherit;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    flex-direction: column;\n    width: 100%;\n    max-height: 100%;\n    aspect-ratio: var(--aspect-ratio);\n    scroll-snap-align: start;\n    scroll-snap-stop: always;\n  }\n\n  ::slotted(img) {\n    width: 100% !important;\n    height: 100% !important;\n    object-fit: cover;\n  }\n`;\n"
  },
  {
    "path": "src/components/carousel-item/carousel-item.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\n\ndescribe('<sl-carousel-item>', () => {\n  it('should render a component', async () => {\n    const el = await fixture(html` <sl-carousel-item></sl-carousel-item> `);\n\n    expect(el).to.exist;\n  });\n\n  it('should pass accessibility tests', async () => {\n    // Arrange\n    const el = await fixture(html` <sl-carousel-item></sl-carousel-item> `);\n\n    // Assert\n    await expect(el).to.be.accessible();\n  });\n});\n"
  },
  {
    "path": "src/components/carousel-item/carousel-item.ts",
    "content": "import SlCarouselItem from './carousel-item.component.js';\n\nexport * from './carousel-item.component.js';\nexport default SlCarouselItem;\n\nSlCarouselItem.define('sl-carousel-item');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-carousel-item': SlCarouselItem;\n  }\n}\n"
  },
  {
    "path": "src/components/checkbox/checkbox.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './checkbox.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Checkboxes allow the user to toggle an option on or off.\n * @documentation https://shoelace.style/components/checkbox\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot - The checkbox's label.\n * @slot help-text - Text that describes how to use the checkbox. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-blur - Emitted when the checkbox loses focus.\n * @event sl-change - Emitted when the checked state changes.\n * @event sl-focus - Emitted when the checkbox gains focus.\n * @event sl-input - Emitted when the checkbox receives input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart base - The component's base wrapper.\n * @csspart control - The square container that wraps the checkbox's checked state.\n * @csspart control--checked - Matches the control part when the checkbox is checked.\n * @csspart control--indeterminate - Matches the control part when the checkbox is indeterminate.\n * @csspart checked-icon - The checked icon, an `<sl-icon>` element.\n * @csspart indeterminate-icon - The indeterminate icon, an `<sl-icon>` element.\n * @csspart label - The container that wraps the checkbox's label.\n * @csspart form-control-help-text - The help text's wrapper.\n */\nexport default class SlCheckbox extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  private readonly formControlController = new FormControlController(this, {\n    value: (control: SlCheckbox) => (control.checked ? control.value || 'on' : undefined),\n    defaultValue: (control: SlCheckbox) => control.defaultChecked,\n    setValue: (control: SlCheckbox, checked: boolean) => (control.checked = checked)\n  });\n  private readonly hasSlotController = new HasSlotController(this, 'help-text');\n\n  @query('input[type=\"checkbox\"]') input: HTMLInputElement;\n\n  @state() private hasFocus = false;\n\n  @property() title = ''; // make reactive to pass through\n\n  /** The name of the checkbox, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** The current value of the checkbox, submitted as a name/value pair with form data. */\n  @property() value: string;\n\n  /** The checkbox's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Disables the checkbox. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Draws the checkbox in a checked state. */\n  @property({ type: Boolean, reflect: true }) checked = false;\n\n  /**\n   * Draws the checkbox in an indeterminate state. This is usually applied to checkboxes that represents a \"select\n   * all/none\" behavior when associated checkboxes have a mix of checked and unchecked states.\n   */\n  @property({ type: Boolean, reflect: true }) indeterminate = false;\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue('checked') defaultChecked = false;\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Makes the checkbox a required field. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** The checkbox's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  firstUpdated() {\n    this.formControlController.updateValidity();\n  }\n\n  private handleClick() {\n    this.checked = !this.checked;\n    this.indeterminate = false;\n    this.emit('sl-change');\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleInput() {\n    this.emit('sl-input');\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Disabled form controls are always valid\n    this.formControlController.setValidity(this.disabled);\n  }\n\n  @watch(['checked', 'indeterminate'], { waitUntilFirstUpdate: true })\n  handleStateChange() {\n    this.input.checked = this.checked; // force a sync update\n    this.input.indeterminate = this.indeterminate; // force a sync update\n    this.formControlController.updateValidity();\n  }\n\n  /** Simulates a click on the checkbox. */\n  click() {\n    this.input.click();\n  }\n\n  /** Sets focus on the checkbox. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the checkbox. */\n  blur() {\n    this.input.blur();\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.input.reportValidity();\n  }\n\n  /**\n   * Sets a custom validation message. The value provided will be shown to the user when the form is submitted. To clear\n   * the custom validation message, call this method with an empty string.\n   */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n\n    //\n    // NOTE: we use a <div> around the label slot because of this Chrome bug.\n    //\n    // https://bugs.chromium.org/p/chromium/issues/detail?id=1413733\n    //\n    return html`\n      <div\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          part=\"base\"\n          class=${classMap({\n            checkbox: true,\n            'checkbox--checked': this.checked,\n            'checkbox--disabled': this.disabled,\n            'checkbox--focused': this.hasFocus,\n            'checkbox--indeterminate': this.indeterminate,\n            'checkbox--small': this.size === 'small',\n            'checkbox--medium': this.size === 'medium',\n            'checkbox--large': this.size === 'large'\n          })}\n        >\n          <input\n            class=\"checkbox__input\"\n            type=\"checkbox\"\n            title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n            name=${this.name}\n            value=${ifDefined(this.value)}\n            .indeterminate=${live(this.indeterminate)}\n            .checked=${live(this.checked)}\n            .disabled=${this.disabled}\n            .required=${this.required}\n            aria-checked=${this.checked ? 'true' : 'false'}\n            aria-describedby=\"help-text\"\n            @click=${this.handleClick}\n            @input=${this.handleInput}\n            @invalid=${this.handleInvalid}\n            @blur=${this.handleBlur}\n            @focus=${this.handleFocus}\n          />\n\n          <span\n            part=\"control${this.checked ? ' control--checked' : ''}${this.indeterminate\n              ? ' control--indeterminate'\n              : ''}\"\n            class=\"checkbox__control\"\n          >\n            ${this.checked\n              ? html`\n                  <sl-icon part=\"checked-icon\" class=\"checkbox__checked-icon\" library=\"system\" name=\"check\"></sl-icon>\n                `\n              : ''}\n            ${!this.checked && this.indeterminate\n              ? html`\n                  <sl-icon\n                    part=\"indeterminate-icon\"\n                    class=\"checkbox__indeterminate-icon\"\n                    library=\"system\"\n                    name=\"indeterminate\"\n                  ></sl-icon>\n                `\n              : ''}\n          </span>\n\n          <div part=\"label\" class=\"checkbox__label\">\n            <slot></slot>\n          </div>\n        </label>\n\n        <div\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n          class=\"form-control__help-text\"\n          id=\"help-text\"\n          part=\"form-control-help-text\"\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/checkbox/checkbox.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  .checkbox {\n    position: relative;\n    display: inline-flex;\n    align-items: flex-start;\n    font-family: var(--sl-input-font-family);\n    font-weight: var(--sl-input-font-weight);\n    color: var(--sl-input-label-color);\n    vertical-align: middle;\n    cursor: pointer;\n  }\n\n  .checkbox--small {\n    --toggle-size: var(--sl-toggle-size-small);\n    font-size: var(--sl-input-font-size-small);\n  }\n\n  .checkbox--medium {\n    --toggle-size: var(--sl-toggle-size-medium);\n    font-size: var(--sl-input-font-size-medium);\n  }\n\n  .checkbox--large {\n    --toggle-size: var(--sl-toggle-size-large);\n    font-size: var(--sl-input-font-size-large);\n  }\n\n  .checkbox__control {\n    flex: 0 0 auto;\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    width: var(--toggle-size);\n    height: var(--toggle-size);\n    border: solid var(--sl-input-border-width) var(--sl-input-border-color);\n    border-radius: 2px;\n    background-color: var(--sl-input-background-color);\n    color: var(--sl-color-neutral-0);\n    transition:\n      var(--sl-transition-fast) border-color,\n      var(--sl-transition-fast) background-color,\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) box-shadow;\n  }\n\n  .checkbox__input {\n    position: absolute;\n    opacity: 0;\n    padding: 0;\n    margin: 0;\n    pointer-events: none;\n  }\n\n  .checkbox__checked-icon,\n  .checkbox__indeterminate-icon {\n    display: inline-flex;\n    width: var(--toggle-size);\n    height: var(--toggle-size);\n  }\n\n  /* Hover */\n  .checkbox:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover {\n    border-color: var(--sl-input-border-color-hover);\n    background-color: var(--sl-input-background-color-hover);\n  }\n\n  /* Focus */\n  .checkbox:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__input:focus-visible ~ .checkbox__control {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  /* Checked/indeterminate */\n  .checkbox--checked .checkbox__control,\n  .checkbox--indeterminate .checkbox__control {\n    border-color: var(--sl-color-primary-600);\n    background-color: var(--sl-color-primary-600);\n  }\n\n  /* Checked/indeterminate + hover */\n  .checkbox.checkbox--checked:not(.checkbox--disabled) .checkbox__control:hover,\n  .checkbox.checkbox--indeterminate:not(.checkbox--disabled) .checkbox__control:hover {\n    border-color: var(--sl-color-primary-500);\n    background-color: var(--sl-color-primary-500);\n  }\n\n  /* Checked/indeterminate + focus */\n  .checkbox.checkbox--checked:not(.checkbox--disabled) .checkbox__input:focus-visible ~ .checkbox__control,\n  .checkbox.checkbox--indeterminate:not(.checkbox--disabled) .checkbox__input:focus-visible ~ .checkbox__control {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  /* Disabled */\n  .checkbox--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .checkbox__label {\n    display: inline-block;\n    color: var(--sl-input-label-color);\n    line-height: var(--toggle-size);\n    margin-inline-start: 0.5em;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  :host([required]) .checkbox__label::after {\n    content: var(--sl-input-required-content);\n    color: var(--sl-input-required-content-color);\n    margin-inline-start: var(--sl-input-required-content-offset);\n  }\n`;\n"
  },
  {
    "path": "src/components/checkbox/checkbox.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { clickOnElement } from '../../internal/test.js';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlCheckbox from './checkbox.js';\n\ndescribe('<sl-checkbox>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox>Checkbox</sl-checkbox> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n\n    expect(el.name).to.equal('');\n    expect(el.value).to.be.undefined;\n    expect(el.title).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.required).to.be.false;\n    expect(el.checked).to.be.false;\n    expect(el.indeterminate).to.be.false;\n    expect(el.defaultChecked).to.be.false;\n    expect(el.helpText).to.equal('');\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox title=\"Test\"></sl-checkbox> `);\n    const input = el.shadowRoot!.querySelector('input')!;\n\n    expect(input.title).to.equal('Test');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox disabled></sl-checkbox> `);\n    const checkbox = el.shadowRoot!.querySelector('input')!;\n\n    expect(checkbox.disabled).to.be.true;\n  });\n\n  it('should be disabled when disabled property is set', async () => {\n    const el = await fixture<SlCheckbox>(html`<sl-checkbox></sl-checkbox>`);\n    const checkbox = el.shadowRoot!.querySelector('input')!;\n\n    el.disabled = true;\n    await el.updateComplete;\n\n    expect(checkbox.disabled).to.be.true;\n  });\n\n  it('should be valid by default', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n    expect(el.checkValidity()).to.be.true;\n  });\n\n  it('should emit sl-change and sl-input when clicked', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.click();\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.true;\n  });\n\n  it('should emit sl-change and sl-input when toggled with spacebar', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.focus();\n    await el.updateComplete;\n    await sendKeys({ press: ' ' });\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.true;\n  });\n\n  it('should not emit sl-change or sl-input when checked programmatically', async () => {\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n\n    el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n    el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n    el.checked = true;\n    await el.updateComplete;\n    el.checked = false;\n    await el.updateComplete;\n  });\n\n  it('should hide the native input with the correct positioning to scroll correctly when contained in an overflow', async () => {\n    //\n    // See: https://github.com/shoelace-style/shoelace/issues/1169\n    //\n    const el = await fixture<SlCheckbox>(html` <sl-checkbox></sl-checkbox> `);\n    const label = el.shadowRoot!.querySelector('.checkbox')!;\n    const input = el.shadowRoot!.querySelector('.checkbox__input')!;\n\n    const labelPosition = getComputedStyle(label).position;\n    const inputPosition = getComputedStyle(input).position;\n\n    expect(labelPosition).to.equal('relative');\n    expect(inputPosition).to.equal('absolute');\n  });\n\n  describe('when submitting a form', () => {\n    it('should submit the correct value when a value is provided', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-checkbox name=\"a\" value=\"1\" checked></sl-checkbox>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => {\n        formData = new FormData(form);\n        event.preventDefault();\n      });\n      let formData: FormData;\n\n      form.addEventListener('submit', submitHandler);\n      button.click();\n\n      await waitUntil(() => submitHandler.calledOnce);\n\n      expect(formData!.get('a')).to.equal('1');\n    });\n\n    it('should submit \"on\" when no value is provided', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-checkbox name=\"a\" checked></sl-checkbox>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => {\n        formData = new FormData(form);\n        event.preventDefault();\n      });\n      let formData: FormData;\n\n      form.addEventListener('submit', submitHandler);\n      button.click();\n\n      await waitUntil(() => submitHandler.calledOnce);\n\n      expect(formData!.get('a')).to.equal('on');\n    });\n\n    it('should be invalid when setCustomValidity() is called with a non-empty value', async () => {\n      const checkbox = await fixture<HTMLFormElement>(html` <sl-checkbox></sl-checkbox> `);\n\n      // Submitting the form after setting custom validity should not trigger the handler\n      checkbox.setCustomValidity('Invalid selection');\n      await checkbox.updateComplete;\n\n      expect(checkbox.checkValidity()).to.be.false;\n      expect(checkbox.checkValidity()).to.be.false;\n      expect(checkbox.hasAttribute('data-invalid')).to.be.true;\n      expect(checkbox.hasAttribute('data-valid')).to.be.false;\n      expect(checkbox.hasAttribute('data-user-invalid')).to.be.false;\n      expect(checkbox.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(checkbox);\n      await checkbox.updateComplete;\n\n      expect(checkbox.hasAttribute('data-user-invalid')).to.be.true;\n      expect(checkbox.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should be invalid when required and unchecked', async () => {\n      const checkbox = await fixture<HTMLFormElement>(html` <sl-checkbox required></sl-checkbox> `);\n      expect(checkbox.checkValidity()).to.be.false;\n    });\n\n    it('should be valid when required and checked', async () => {\n      const checkbox = await fixture<HTMLFormElement>(html` <sl-checkbox required checked></sl-checkbox> `);\n      expect(checkbox.checkValidity()).to.be.true;\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-checkbox form=\"f\" name=\"a\" value=\"1\" checked></sl-checkbox>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('1');\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-checkbox required></sl-checkbox></form> `);\n      const checkbox = el.querySelector<SlCheckbox>('sl-checkbox')!;\n\n      expect(checkbox.hasAttribute('data-required')).to.be.true;\n      expect(checkbox.hasAttribute('data-optional')).to.be.false;\n      expect(checkbox.hasAttribute('data-invalid')).to.be.true;\n      expect(checkbox.hasAttribute('data-valid')).to.be.false;\n      expect(checkbox.hasAttribute('data-user-invalid')).to.be.false;\n      expect(checkbox.hasAttribute('data-user-valid')).to.be.false;\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-checkbox name=\"a\" value=\"1\" checked></sl-checkbox>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const checkbox = form.querySelector('sl-checkbox')!;\n      checkbox.checked = false;\n\n      await checkbox.updateComplete;\n      setTimeout(() => button.click());\n\n      await oneEvent(form, 'reset');\n      await checkbox.updateComplete;\n\n      expect(checkbox.checked).to.true;\n\n      checkbox.defaultChecked = false;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await checkbox.updateComplete;\n\n      expect(checkbox.checked).to.false;\n    });\n  });\n\n  describe('click', () => {\n    it('should click the inner input', async () => {\n      const el = await fixture<SlCheckbox>(html`<sl-checkbox></sl-checkbox>`);\n      const checkbox = el.shadowRoot!.querySelector('input')!;\n      const clickSpy = sinon.spy();\n\n      checkbox.addEventListener('click', clickSpy, { once: true });\n\n      el.click();\n      await el.updateComplete;\n\n      expect(clickSpy.called).to.equal(true);\n      expect(el.checked).to.equal(true);\n    });\n  });\n\n  describe('focus', () => {\n    it('should focus the inner input', async () => {\n      const el = await fixture<SlCheckbox>(html`<sl-checkbox></sl-checkbox>`);\n      const checkbox = el.shadowRoot!.querySelector('input')!;\n      const focusSpy = sinon.spy();\n\n      checkbox.addEventListener('focus', focusSpy, { once: true });\n\n      el.focus();\n      await el.updateComplete;\n\n      expect(focusSpy.called).to.equal(true);\n      expect(el.shadowRoot!.activeElement).to.equal(checkbox);\n    });\n\n    it('should not jump the page to the bottom when focusing a checkbox at the bottom of an element with overflow: auto;', async () => {\n      // https://github.com/shoelace-style/shoelace/issues/1169\n      const el = await fixture<HTMLDivElement>(html`\n        <div style=\"display: flex; flex-direction: column; overflow: auto; max-height: 400px; gap: 8px;\">\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n          <sl-checkbox>Checkbox</sl-checkbox>\n        </div>\n        ;\n      `);\n\n      const checkboxes = el.querySelectorAll<SlCheckbox>('sl-checkbox');\n      const lastSwitch = checkboxes[checkboxes.length - 1];\n\n      expect(window.scrollY).to.equal(0);\n      // Without these 2 timeouts, tests will pass unexpectedly in Safari.\n      await aTimeout(10);\n      lastSwitch.focus();\n      await aTimeout(10);\n      expect(window.scrollY).to.equal(0);\n    });\n  });\n\n  describe('blur', () => {\n    it('should blur the inner input', async () => {\n      const el = await fixture<SlCheckbox>(html`<sl-checkbox></sl-checkbox>`);\n      const checkbox = el.shadowRoot!.querySelector('input')!;\n      const blurSpy = sinon.spy();\n\n      checkbox.addEventListener('blur', blurSpy, { once: true });\n\n      el.focus();\n      await el.updateComplete;\n\n      el.blur();\n      await el.updateComplete;\n\n      expect(blurSpy.called).to.equal(true);\n      expect(el.shadowRoot!.activeElement).to.equal(null);\n    });\n  });\n\n  describe('indeterminate', () => {\n    it('should render indeterminate icon until checked', async () => {\n      const el = await fixture<SlCheckbox>(html`<sl-checkbox indeterminate></sl-checkbox>`);\n      let indeterminateIcon = el.shadowRoot!.querySelector('[part~=\"indeterminate-icon\"]')!;\n\n      expect(indeterminateIcon).not.to.be.null;\n\n      el.click();\n      await el.updateComplete;\n\n      indeterminateIcon = el.shadowRoot!.querySelector('[part~=\"indeterminate-icon\"]')!;\n\n      expect(indeterminateIcon).to.be.null;\n    });\n\n    runFormControlBaseTests('sl-checkbox');\n  });\n});\n"
  },
  {
    "path": "src/components/checkbox/checkbox.ts",
    "content": "import SlCheckbox from './checkbox.component.js';\n\nexport * from './checkbox.component.js';\nexport default SlCheckbox;\n\nSlCheckbox.define('sl-checkbox');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-checkbox': SlCheckbox;\n  }\n}\n"
  },
  {
    "path": "src/components/color-picker/color-picker.component.ts",
    "content": "import { clamp } from '../../internal/math.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { drag } from '../../internal/drag.js';\nimport { eventOptions, property, query, state } from 'lit/decorators.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { TinyColor } from '@ctrl/tinycolor';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlButton from '../button/button.component.js';\nimport SlButtonGroup from '../button-group/button-group.component.js';\nimport SlDropdown from '../dropdown/dropdown.component.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlInput from '../input/input.component.js';\nimport SlVisuallyHidden from '../visually-hidden/visually-hidden.component.js';\nimport styles from './color-picker.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\nimport type { SlChangeEvent } from '../../events/sl-change.js';\nimport type { SlInputEvent } from '../../events/sl-input.js';\n\nconst hasEyeDropper = 'EyeDropper' in window;\n\ninterface EyeDropperConstructor {\n  new (): EyeDropperInterface;\n}\n\ninterface EyeDropperInterface {\n  open: () => Promise<{ sRGBHex: string }>;\n}\n\ndeclare const EyeDropper: EyeDropperConstructor;\n\n/**\n * @summary Color pickers allow the user to select a color.\n * @documentation https://shoelace.style/components/color-picker\n * @status stable\n * @since 2.0\n *\n * @dependency sl-button\n * @dependency sl-button-group\n * @dependency sl-dropdown\n * @dependency sl-input\n * @dependency sl-visually-hidden\n *\n * @slot label - The color picker's form label. Alternatively, you can use the `label` attribute.\n *\n * @event sl-blur - Emitted when the color picker loses focus.\n * @event sl-change - Emitted when the color picker's value changes.\n * @event sl-focus - Emitted when the color picker receives focus.\n * @event sl-input - Emitted when the color picker receives input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart base - The component's base wrapper.\n * @csspart trigger - The color picker's dropdown trigger.\n * @csspart swatches - The container that holds the swatches.\n * @csspart swatch - Each individual swatch.\n * @csspart grid - The color grid.\n * @csspart grid-handle - The color grid's handle.\n * @csspart slider - Hue and opacity sliders.\n * @csspart slider-handle - Hue and opacity slider handles.\n * @csspart hue-slider - The hue slider.\n * @csspart hue-slider-handle - The hue slider's handle.\n * @csspart opacity-slider - The opacity slider.\n * @csspart opacity-slider-handle - The opacity slider's handle.\n * @csspart preview - The preview color.\n * @csspart input - The text input.\n * @csspart eye-dropper-button - The eye dropper button.\n * @csspart eye-dropper-button__base - The eye dropper button's exported `button` part.\n * @csspart eye-dropper-button__prefix - The eye dropper button's exported `prefix` part.\n * @csspart eye-dropper-button__label - The eye dropper button's exported `label` part.\n * @csspart eye-dropper-button__suffix - The eye dropper button's exported `suffix` part.\n * @csspart eye-dropper-button__caret - The eye dropper button's exported `caret` part.\n * @csspart format-button - The format button.\n * @csspart format-button__base - The format button's exported `button` part.\n * @csspart format-button__prefix - The format button's exported `prefix` part.\n * @csspart format-button__label - The format button's exported `label` part.\n * @csspart format-button__suffix - The format button's exported `suffix` part.\n * @csspart format-button__caret - The format button's exported `caret` part.\n *\n * @cssproperty --grid-width - The width of the color grid.\n * @cssproperty --grid-height - The height of the color grid.\n * @cssproperty --grid-handle-size - The size of the color grid's handle.\n * @cssproperty --slider-height - The height of the hue and alpha sliders.\n * @cssproperty --slider-handle-size - The diameter of the slider's handle.\n * @cssproperty --swatch-size - The size of each predefined color swatch.\n */\nexport default class SlColorPicker extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  static dependencies = {\n    'sl-button-group': SlButtonGroup,\n    'sl-button': SlButton,\n    'sl-dropdown': SlDropdown,\n    'sl-icon': SlIcon,\n    'sl-input': SlInput,\n    'sl-visually-hidden': SlVisuallyHidden\n  };\n\n  private readonly formControlController = new FormControlController(this);\n  private isSafeValue = false;\n  private readonly localize = new LocalizeController(this);\n\n  @query('[part~=\"base\"]') base: HTMLElement;\n  @query('[part~=\"input\"]') input: SlInput;\n  @query('.color-dropdown') dropdown: SlDropdown;\n  @query('[part~=\"preview\"]') previewButton: HTMLButtonElement;\n  @query('[part~=\"trigger\"]') trigger: HTMLButtonElement;\n\n  @state() private hasFocus = false;\n  @state() private isDraggingGridHandle = false;\n  @state() private isEmpty = false;\n  @state() private inputValue = '';\n  @state() private hue = 0;\n  @state() private saturation = 100;\n  @state() private brightness = 100;\n  @state() private alpha = 100;\n\n  /**\n   * The current value of the color picker. The value's format will vary based the `format` attribute. To get the value\n   * in a specific format, use the `getFormattedValue()` method. The value is submitted as a name/value pair with form\n   * data.\n   */\n  @property() value = '';\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue() defaultValue = '';\n\n  /**\n   * The color picker's label. This will not be displayed, but it will be announced by assistive devices. If you need to\n   * display HTML, you can use the `label` slot` instead.\n   */\n  @property() label = '';\n\n  /**\n   * The format to use. If opacity is enabled, these will translate to HEXA, RGBA, HSLA, and HSVA respectively. The color\n   * picker will accept user input in any format (including CSS color names) and convert it to the desired format.\n   */\n  @property() format: 'hex' | 'rgb' | 'hsl' | 'hsv' = 'hex';\n\n  /** Renders the color picker inline rather than in a dropdown. */\n  @property({ type: Boolean, reflect: true }) inline = false;\n\n  /** Determines the size of the color picker's trigger. This has no effect on inline color pickers. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Removes the button that lets users toggle between format.   */\n  @property({ attribute: 'no-format-toggle', type: Boolean }) noFormatToggle = false;\n\n  /** The name of the form control, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** Disables the color picker. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * Enable this option to prevent the panel from being clipped when the component is placed inside a container with\n   * `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.\n   */\n  @property({ type: Boolean }) hoist = false;\n\n  /** Shows the opacity slider. Enabling this will cause the formatted value to be HEXA, RGBA, or HSLA. */\n  @property({ type: Boolean }) opacity = false;\n\n  /** By default, values are lowercase. With this attribute, values will be uppercase instead. */\n  @property({ type: Boolean }) uppercase = false;\n\n  /**\n   * One or more predefined color swatches to display as presets in the color picker. Can include any format the color\n   * picker can parse, including HEX(A), RGB(A), HSL(A), HSV(A), and CSS color names. Each color must be separated by a\n   * semicolon (`;`). Alternatively, you can pass an array of color values to this property using JavaScript.\n   */\n  @property() swatches: string | string[] = '';\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Makes the color picker a required field. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  constructor() {\n    super();\n    this.addEventListener('focusin', this.handleFocusIn);\n    this.addEventListener('focusout', this.handleFocusOut);\n  }\n\n  firstUpdated() {\n    this.input.updateComplete.then(() => {\n      this.formControlController.updateValidity();\n    });\n  }\n\n  private handleCopy() {\n    this.input.select();\n    document.execCommand('copy');\n    this.previewButton.focus();\n\n    // Show copied animation\n    this.previewButton.classList.add('color-picker__preview-color--copied');\n    this.previewButton.addEventListener('animationend', () => {\n      this.previewButton.classList.remove('color-picker__preview-color--copied');\n    });\n  }\n\n  private handleFocusIn = () => {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  };\n\n  private handleFocusOut = () => {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  };\n\n  private handleFormatToggle() {\n    const formats = ['hex', 'rgb', 'hsl', 'hsv'];\n    const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;\n    this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv';\n    this.setColor(this.value);\n    this.emit('sl-change');\n    this.emit('sl-input');\n  }\n\n  private handleAlphaDrag(event: PointerEvent) {\n    const container = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__slider.color-picker__alpha')!;\n    const handle = container.querySelector<HTMLElement>('.color-picker__slider-handle')!;\n    const { width } = container.getBoundingClientRect();\n    let initialValue = this.value;\n    let currentValue = this.value;\n\n    handle.focus();\n    event.preventDefault();\n\n    drag(container, {\n      onMove: x => {\n        this.alpha = clamp((x / width) * 100, 0, 100);\n        this.syncValues();\n\n        if (this.value !== currentValue) {\n          currentValue = this.value;\n          this.emit('sl-input');\n        }\n      },\n      onStop: () => {\n        if (this.value !== initialValue) {\n          initialValue = this.value;\n          this.emit('sl-change');\n        }\n      },\n      initialEvent: event\n    });\n  }\n\n  private handleHueDrag(event: PointerEvent) {\n    const container = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__slider.color-picker__hue')!;\n    const handle = container.querySelector<HTMLElement>('.color-picker__slider-handle')!;\n    const { width } = container.getBoundingClientRect();\n    let initialValue = this.value;\n    let currentValue = this.value;\n\n    handle.focus();\n    event.preventDefault();\n\n    drag(container, {\n      onMove: x => {\n        this.hue = clamp((x / width) * 360, 0, 360);\n        this.syncValues();\n\n        if (this.value !== currentValue) {\n          currentValue = this.value;\n          this.emit('sl-input');\n        }\n      },\n      onStop: () => {\n        if (this.value !== initialValue) {\n          initialValue = this.value;\n          this.emit('sl-change');\n        }\n      },\n      initialEvent: event\n    });\n  }\n\n  private handleGridDrag(event: PointerEvent) {\n    const grid = this.shadowRoot!.querySelector<HTMLElement>('.color-picker__grid')!;\n    const handle = grid.querySelector<HTMLElement>('.color-picker__grid-handle')!;\n    const { width, height } = grid.getBoundingClientRect();\n    let initialValue = this.value;\n    let currentValue = this.value;\n\n    handle.focus();\n    event.preventDefault();\n\n    this.isDraggingGridHandle = true;\n\n    drag(grid, {\n      onMove: (x, y) => {\n        this.saturation = clamp((x / width) * 100, 0, 100);\n        this.brightness = clamp(100 - (y / height) * 100, 0, 100);\n        this.syncValues();\n\n        if (this.value !== currentValue) {\n          currentValue = this.value;\n          this.emit('sl-input');\n        }\n      },\n      onStop: () => {\n        this.isDraggingGridHandle = false;\n        if (this.value !== initialValue) {\n          initialValue = this.value;\n          this.emit('sl-change');\n        }\n      },\n      initialEvent: event\n    });\n  }\n\n  private handleAlphaKeyDown(event: KeyboardEvent) {\n    const increment = event.shiftKey ? 10 : 1;\n    const oldValue = this.value;\n\n    if (event.key === 'ArrowLeft') {\n      event.preventDefault();\n      this.alpha = clamp(this.alpha - increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (event.key === 'ArrowRight') {\n      event.preventDefault();\n      this.alpha = clamp(this.alpha + increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (event.key === 'Home') {\n      event.preventDefault();\n      this.alpha = 0;\n      this.syncValues();\n    }\n\n    if (event.key === 'End') {\n      event.preventDefault();\n      this.alpha = 100;\n      this.syncValues();\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  private handleHueKeyDown(event: KeyboardEvent) {\n    const increment = event.shiftKey ? 10 : 1;\n    const oldValue = this.value;\n\n    if (event.key === 'ArrowLeft') {\n      event.preventDefault();\n      this.hue = clamp(this.hue - increment, 0, 360);\n      this.syncValues();\n    }\n\n    if (event.key === 'ArrowRight') {\n      event.preventDefault();\n      this.hue = clamp(this.hue + increment, 0, 360);\n      this.syncValues();\n    }\n\n    if (event.key === 'Home') {\n      event.preventDefault();\n      this.hue = 0;\n      this.syncValues();\n    }\n\n    if (event.key === 'End') {\n      event.preventDefault();\n      this.hue = 360;\n      this.syncValues();\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  private handleGridKeyDown(event: KeyboardEvent) {\n    const increment = event.shiftKey ? 10 : 1;\n    const oldValue = this.value;\n\n    if (event.key === 'ArrowLeft') {\n      event.preventDefault();\n      this.saturation = clamp(this.saturation - increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (event.key === 'ArrowRight') {\n      event.preventDefault();\n      this.saturation = clamp(this.saturation + increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (event.key === 'ArrowUp') {\n      event.preventDefault();\n      this.brightness = clamp(this.brightness + increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (event.key === 'ArrowDown') {\n      event.preventDefault();\n      this.brightness = clamp(this.brightness - increment, 0, 100);\n      this.syncValues();\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  private handleInputChange(event: SlChangeEvent) {\n    const target = event.target as HTMLInputElement;\n    const oldValue = this.value;\n\n    // Prevent the <sl-input>'s sl-change event from bubbling up\n    event.stopPropagation();\n\n    if (this.input.value) {\n      this.setColor(target.value);\n      target.value = this.value;\n    } else {\n      this.value = '';\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  private handleInputInput(event: SlInputEvent) {\n    this.formControlController.updateValidity();\n\n    // Prevent the <sl-input>'s sl-input event from bubbling up\n    event.stopPropagation();\n  }\n\n  private handleInputKeyDown(event: KeyboardEvent) {\n    if (event.key === 'Enter') {\n      const oldValue = this.value;\n\n      if (this.input.value) {\n        this.setColor(this.input.value);\n        this.input.value = this.value;\n\n        if (this.value !== oldValue) {\n          this.emit('sl-change');\n          this.emit('sl-input');\n        }\n\n        setTimeout(() => this.input.select());\n      } else {\n        this.hue = 0;\n      }\n    }\n  }\n\n  private handleInputInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  @eventOptions({ passive: false })\n  private handleTouchMove(event: TouchEvent) {\n    event.preventDefault();\n  }\n\n  private parseColor(colorString: string) {\n    const color = new TinyColor(colorString);\n    if (!color.isValid) {\n      return null;\n    }\n\n    const hslColor = color.toHsl();\n    // Adjust saturation and lightness from 0-1 to 0-100\n    const hsl = {\n      h: hslColor.h,\n      s: hslColor.s * 100,\n      l: hslColor.l * 100,\n      a: hslColor.a\n    };\n\n    const rgb = color.toRgb();\n\n    const hex = color.toHexString();\n    const hexa = color.toHex8String();\n\n    const hsvColor = color.toHsv();\n    // Adjust saturation and value from 0-1 to 0-100\n    const hsv = {\n      h: hsvColor.h,\n      s: hsvColor.s * 100,\n      v: hsvColor.v * 100,\n      a: hsvColor.a\n    };\n\n    return {\n      hsl: {\n        h: hsl.h,\n        s: hsl.s,\n        l: hsl.l,\n        string: this.setLetterCase(`hsl(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`)\n      },\n      hsla: {\n        h: hsl.h,\n        s: hsl.s,\n        l: hsl.l,\n        a: hsl.a,\n        string: this.setLetterCase(\n          `hsla(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%, ${hsl.a.toFixed(2).toString()})`\n        )\n      },\n      hsv: {\n        h: hsv.h,\n        s: hsv.s,\n        v: hsv.v,\n        string: this.setLetterCase(`hsv(${Math.round(hsv.h)}, ${Math.round(hsv.s)}%, ${Math.round(hsv.v)}%)`)\n      },\n      hsva: {\n        h: hsv.h,\n        s: hsv.s,\n        v: hsv.v,\n        a: hsv.a,\n        string: this.setLetterCase(\n          `hsva(${Math.round(hsv.h)}, ${Math.round(hsv.s)}%, ${Math.round(hsv.v)}%, ${hsv.a.toFixed(2).toString()})`\n        )\n      },\n      rgb: {\n        r: rgb.r,\n        g: rgb.g,\n        b: rgb.b,\n        string: this.setLetterCase(`rgb(${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)})`)\n      },\n      rgba: {\n        r: rgb.r,\n        g: rgb.g,\n        b: rgb.b,\n        a: rgb.a,\n        string: this.setLetterCase(\n          `rgba(${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)}, ${rgb.a.toFixed(2).toString()})`\n        )\n      },\n      hex: this.setLetterCase(hex),\n      hexa: this.setLetterCase(hexa)\n    };\n  }\n\n  private setColor(colorString: string) {\n    const newColor = this.parseColor(colorString);\n\n    if (newColor === null) {\n      return false;\n    }\n\n    this.hue = newColor.hsva.h;\n    this.saturation = newColor.hsva.s;\n    this.brightness = newColor.hsva.v;\n    this.alpha = this.opacity ? newColor.hsva.a * 100 : 100;\n\n    this.syncValues();\n\n    return true;\n  }\n\n  private setLetterCase(string: string) {\n    if (typeof string !== 'string') {\n      return '';\n    }\n    return this.uppercase ? string.toUpperCase() : string.toLowerCase();\n  }\n\n  private async syncValues() {\n    const currentColor = this.parseColor(\n      `hsva(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha / 100})`\n    );\n\n    if (currentColor === null) {\n      return;\n    }\n\n    // Update the value\n    if (this.format === 'hsl') {\n      this.inputValue = this.opacity ? currentColor.hsla.string : currentColor.hsl.string;\n    } else if (this.format === 'rgb') {\n      this.inputValue = this.opacity ? currentColor.rgba.string : currentColor.rgb.string;\n    } else if (this.format === 'hsv') {\n      this.inputValue = this.opacity ? currentColor.hsva.string : currentColor.hsv.string;\n    } else {\n      this.inputValue = this.opacity ? currentColor.hexa : currentColor.hex;\n    }\n\n    // Setting this.value will trigger the watcher which parses the new value. We want to bypass that behavior because\n    // we've already parsed the color here and conversion/rounding can lead to values changing slightly. When this\n    // happens, dragging the grid handle becomes jumpy. After the next update, the usual behavior is restored.\n    this.isSafeValue = true;\n    this.value = this.inputValue;\n    await this.updateComplete;\n    this.isSafeValue = false;\n  }\n\n  private handleAfterHide() {\n    this.previewButton.classList.remove('color-picker__preview-color--copied');\n  }\n\n  private handleEyeDropper() {\n    if (!hasEyeDropper) {\n      return;\n    }\n\n    const eyeDropper = new EyeDropper();\n\n    eyeDropper\n      .open()\n      .then(colorSelectionResult => {\n        const oldValue = this.value;\n\n        this.setColor(colorSelectionResult.sRGBHex);\n\n        if (this.value !== oldValue) {\n          this.emit('sl-change');\n          this.emit('sl-input');\n        }\n      })\n      .catch(() => {\n        // The user canceled, do nothing\n      });\n  }\n\n  private selectSwatch(color: string) {\n    const oldValue = this.value;\n\n    if (!this.disabled) {\n      this.setColor(color);\n\n      if (this.value !== oldValue) {\n        this.emit('sl-change');\n        this.emit('sl-input');\n      }\n    }\n  }\n\n  /** Generates a hex string from HSV values. Hue must be 0-360. All other arguments must be 0-100. */\n  private getHexString(hue: number, saturation: number, brightness: number, alpha = 100) {\n    const color = new TinyColor(`hsva(${hue}, ${saturation}%, ${brightness}%, ${alpha / 100})`);\n    if (!color.isValid) {\n      return '';\n    }\n\n    return color.toHex8String();\n  }\n\n  // Prevents nested components from leaking events\n  private stopNestedEventPropagation(event: CustomEvent) {\n    event.stopImmediatePropagation();\n  }\n\n  @watch('format', { waitUntilFirstUpdate: true })\n  handleFormatChange() {\n    this.syncValues();\n  }\n\n  @watch('opacity', { waitUntilFirstUpdate: true })\n  handleOpacityChange() {\n    this.alpha = 100;\n  }\n\n  @watch('value')\n  handleValueChange(oldValue: string | undefined, newValue: string) {\n    this.isEmpty = !newValue;\n\n    if (!newValue) {\n      this.hue = 0;\n      this.saturation = 0;\n      this.brightness = 100;\n      this.alpha = 100;\n    }\n\n    if (!this.isSafeValue) {\n      const newColor = this.parseColor(newValue);\n\n      if (newColor !== null) {\n        this.inputValue = this.value;\n        this.hue = newColor.hsva.h;\n        this.saturation = newColor.hsva.s;\n        this.brightness = newColor.hsva.v;\n        this.alpha = newColor.hsva.a * 100;\n        this.syncValues();\n      } else {\n        this.inputValue = oldValue ?? '';\n      }\n    }\n  }\n\n  /** Sets focus on the color picker. */\n  focus(options?: FocusOptions) {\n    if (this.inline) {\n      this.base.focus(options);\n    } else {\n      this.trigger.focus(options);\n    }\n  }\n\n  /** Removes focus from the color picker. */\n  blur() {\n    const elementToBlur = this.inline ? this.base : this.trigger;\n\n    if (this.hasFocus) {\n      // We don't know which element in the color picker has focus, so we'll move it to the trigger or base (inline) and\n      // blur that instead. This results in document.activeElement becoming the <body>. This doesn't cause another focus\n      // event because we're using focusin and something inside the color picker already has focus.\n      elementToBlur.focus({ preventScroll: true });\n      elementToBlur.blur();\n    }\n\n    if (this.dropdown?.open) {\n      this.dropdown.hide();\n    }\n  }\n\n  /** Returns the current value as a string in the specified format. */\n  getFormattedValue(format: 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hsv' | 'hsva' = 'hex') {\n    const currentColor = this.parseColor(\n      `hsva(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha / 100})`\n    );\n\n    if (currentColor === null) {\n      return '';\n    }\n\n    switch (format) {\n      case 'hex':\n        return currentColor.hex;\n      case 'hexa':\n        return currentColor.hexa;\n      case 'rgb':\n        return currentColor.rgb.string;\n      case 'rgba':\n        return currentColor.rgba.string;\n      case 'hsl':\n        return currentColor.hsl.string;\n      case 'hsla':\n        return currentColor.hsla.string;\n      case 'hsv':\n        return currentColor.hsv.string;\n      case 'hsva':\n        return currentColor.hsva.string;\n      default:\n        return '';\n    }\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    if (!this.inline && !this.validity.valid) {\n      // If the input is inline and invalid, show the dropdown so the browser can focus on it\n      this.dropdown.show();\n      this.addEventListener('sl-after-show', () => this.input.reportValidity(), { once: true });\n\n      if (!this.disabled) {\n        // By standards we have to emit a `sl-invalid` event here synchronously.\n        this.formControlController.emitInvalidEvent();\n      }\n\n      return false;\n    }\n\n    return this.input.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const gridHandleX = this.saturation;\n    const gridHandleY = 100 - this.brightness;\n    const swatches = Array.isArray(this.swatches)\n      ? this.swatches // allow arrays for legacy purposes\n      : this.swatches.split(';').filter(color => color.trim() !== '');\n\n    const colorPicker = html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          'color-picker': true,\n          'color-picker--inline': this.inline,\n          'color-picker--disabled': this.disabled,\n          'color-picker--focused': this.hasFocus\n        })}\n        aria-disabled=${this.disabled ? 'true' : 'false'}\n        aria-labelledby=\"label\"\n        tabindex=${this.inline ? '0' : '-1'}\n      >\n        ${this.inline\n          ? html`\n              <sl-visually-hidden id=\"label\">\n                <slot name=\"label\">${this.label}</slot>\n              </sl-visually-hidden>\n            `\n          : null}\n\n        <div\n          part=\"grid\"\n          class=\"color-picker__grid\"\n          style=${styleMap({ backgroundColor: this.getHexString(this.hue, 100, 100) })}\n          @pointerdown=${this.handleGridDrag}\n          @touchmove=${this.handleTouchMove}\n        >\n          <span\n            part=\"grid-handle\"\n            class=${classMap({\n              'color-picker__grid-handle': true,\n              'color-picker__grid-handle--dragging': this.isDraggingGridHandle\n            })}\n            style=${styleMap({\n              top: `${gridHandleY}%`,\n              left: `${gridHandleX}%`,\n              backgroundColor: this.getHexString(this.hue, this.saturation, this.brightness, this.alpha)\n            })}\n            role=\"application\"\n            aria-label=\"HSV\"\n            tabindex=${ifDefined(this.disabled ? undefined : '0')}\n            @keydown=${this.handleGridKeyDown}\n          ></span>\n        </div>\n\n        <div class=\"color-picker__controls\">\n          <div class=\"color-picker__sliders\">\n            <div\n              part=\"slider hue-slider\"\n              class=\"color-picker__hue color-picker__slider\"\n              @pointerdown=${this.handleHueDrag}\n              @touchmove=${this.handleTouchMove}\n            >\n              <span\n                part=\"slider-handle hue-slider-handle\"\n                class=\"color-picker__slider-handle\"\n                style=${styleMap({\n                  left: `${this.hue === 0 ? 0 : 100 / (360 / this.hue)}%`\n                })}\n                role=\"slider\"\n                aria-label=\"hue\"\n                aria-orientation=\"horizontal\"\n                aria-valuemin=\"0\"\n                aria-valuemax=\"360\"\n                aria-valuenow=${`${Math.round(this.hue)}`}\n                tabindex=${ifDefined(this.disabled ? undefined : '0')}\n                @keydown=${this.handleHueKeyDown}\n              ></span>\n            </div>\n\n            ${this.opacity\n              ? html`\n                  <div\n                    part=\"slider opacity-slider\"\n                    class=\"color-picker__alpha color-picker__slider color-picker__transparent-bg\"\n                    @pointerdown=\"${this.handleAlphaDrag}\"\n                    @touchmove=${this.handleTouchMove}\n                  >\n                    <div\n                      class=\"color-picker__alpha-gradient\"\n                      style=${styleMap({\n                        backgroundImage: `linear-gradient(\n                          to right,\n                          ${this.getHexString(this.hue, this.saturation, this.brightness, 0)} 0%,\n                          ${this.getHexString(this.hue, this.saturation, this.brightness, 100)} 100%\n                        )`\n                      })}\n                    ></div>\n                    <span\n                      part=\"slider-handle opacity-slider-handle\"\n                      class=\"color-picker__slider-handle\"\n                      style=${styleMap({\n                        left: `${this.alpha}%`\n                      })}\n                      role=\"slider\"\n                      aria-label=\"alpha\"\n                      aria-orientation=\"horizontal\"\n                      aria-valuemin=\"0\"\n                      aria-valuemax=\"100\"\n                      aria-valuenow=${Math.round(this.alpha)}\n                      tabindex=${ifDefined(this.disabled ? undefined : '0')}\n                      @keydown=${this.handleAlphaKeyDown}\n                    ></span>\n                  </div>\n                `\n              : ''}\n          </div>\n\n          <button\n            type=\"button\"\n            part=\"preview\"\n            class=\"color-picker__preview color-picker__transparent-bg\"\n            aria-label=${this.localize.term('copy')}\n            style=${styleMap({\n              '--preview-color': this.getHexString(this.hue, this.saturation, this.brightness, this.alpha)\n            })}\n            @click=${this.handleCopy}\n          ></button>\n        </div>\n\n        <div class=\"color-picker__user-input\" aria-live=\"polite\">\n          <sl-input\n            part=\"input\"\n            type=\"text\"\n            name=${this.name}\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            autocapitalize=\"off\"\n            spellcheck=\"false\"\n            value=${this.isEmpty ? '' : this.inputValue}\n            ?required=${this.required}\n            ?disabled=${this.disabled}\n            aria-label=${this.localize.term('currentValue')}\n            @keydown=${this.handleInputKeyDown}\n            @sl-change=${this.handleInputChange}\n            @sl-input=${this.handleInputInput}\n            @sl-invalid=${this.handleInputInvalid}\n            @sl-blur=${this.stopNestedEventPropagation}\n            @sl-focus=${this.stopNestedEventPropagation}\n          ></sl-input>\n\n          <sl-button-group>\n            ${!this.noFormatToggle\n              ? html`\n                  <sl-button\n                    part=\"format-button\"\n                    aria-label=${this.localize.term('toggleColorFormat')}\n                    exportparts=\"\n                      base:format-button__base,\n                      prefix:format-button__prefix,\n                      label:format-button__label,\n                      suffix:format-button__suffix,\n                      caret:format-button__caret\n                    \"\n                    @click=${this.handleFormatToggle}\n                    @sl-blur=${this.stopNestedEventPropagation}\n                    @sl-focus=${this.stopNestedEventPropagation}\n                  >\n                    ${this.setLetterCase(this.format)}\n                  </sl-button>\n                `\n              : ''}\n            ${hasEyeDropper\n              ? html`\n                  <sl-button\n                    part=\"eye-dropper-button\"\n                    exportparts=\"\n                      base:eye-dropper-button__base,\n                      prefix:eye-dropper-button__prefix,\n                      label:eye-dropper-button__label,\n                      suffix:eye-dropper-button__suffix,\n                      caret:eye-dropper-button__caret\n                    \"\n                    @click=${this.handleEyeDropper}\n                    @sl-blur=${this.stopNestedEventPropagation}\n                    @sl-focus=${this.stopNestedEventPropagation}\n                  >\n                    <sl-icon\n                      library=\"system\"\n                      name=\"eyedropper\"\n                      label=${this.localize.term('selectAColorFromTheScreen')}\n                    ></sl-icon>\n                  </sl-button>\n                `\n              : ''}\n          </sl-button-group>\n        </div>\n\n        ${swatches.length > 0\n          ? html`\n              <div part=\"swatches\" class=\"color-picker__swatches\">\n                ${swatches.map(swatch => {\n                  const parsedColor = this.parseColor(swatch);\n\n                  // If we can't parse it, skip it\n                  if (!parsedColor) {\n                    console.error(`Unable to parse swatch color: \"${swatch}\"`, this);\n                    return '';\n                  }\n\n                  return html`\n                    <div\n                      part=\"swatch\"\n                      class=\"color-picker__swatch color-picker__transparent-bg\"\n                      tabindex=${ifDefined(this.disabled ? undefined : '0')}\n                      role=\"button\"\n                      aria-label=${swatch}\n                      @click=${() => this.selectSwatch(swatch)}\n                      @keydown=${(event: KeyboardEvent) =>\n                        !this.disabled && event.key === 'Enter' && this.setColor(parsedColor.hexa)}\n                    >\n                      <div\n                        class=\"color-picker__swatch-color\"\n                        style=${styleMap({ backgroundColor: parsedColor.hexa })}\n                      ></div>\n                    </div>\n                  `;\n                })}\n              </div>\n            `\n          : ''}\n      </div>\n    `;\n\n    // Render inline\n    if (this.inline) {\n      return colorPicker;\n    }\n\n    // Render as a dropdown\n    return html`\n      <sl-dropdown\n        class=\"color-dropdown\"\n        aria-disabled=${this.disabled ? 'true' : 'false'}\n        .containingElement=${this}\n        ?disabled=${this.disabled}\n        ?hoist=${this.hoist}\n        @sl-after-hide=${this.handleAfterHide}\n      >\n        <button\n          part=\"trigger\"\n          slot=\"trigger\"\n          class=${classMap({\n            'color-dropdown__trigger': true,\n            'color-dropdown__trigger--disabled': this.disabled,\n            'color-dropdown__trigger--small': this.size === 'small',\n            'color-dropdown__trigger--medium': this.size === 'medium',\n            'color-dropdown__trigger--large': this.size === 'large',\n            'color-dropdown__trigger--empty': this.isEmpty,\n            'color-dropdown__trigger--focused': this.hasFocus,\n            'color-picker__transparent-bg': true\n          })}\n          style=${styleMap({\n            color: this.getHexString(this.hue, this.saturation, this.brightness, this.alpha)\n          })}\n          type=\"button\"\n        >\n          <sl-visually-hidden>\n            <slot name=\"label\">${this.label}</slot>\n          </sl-visually-hidden>\n        </button>\n        ${colorPicker}\n      </sl-dropdown>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/color-picker/color-picker.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --grid-width: 280px;\n    --grid-height: 200px;\n    --grid-handle-size: 16px;\n    --slider-height: 15px;\n    --slider-handle-size: 17px;\n    --swatch-size: 25px;\n\n    display: inline-block;\n  }\n\n  .color-picker {\n    width: var(--grid-width);\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    color: var(--color);\n    background-color: var(--sl-panel-background-color);\n    border-radius: var(--sl-border-radius-medium);\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  .color-picker--inline {\n    border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);\n  }\n\n  .color-picker--inline:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .color-picker__grid {\n    position: relative;\n    height: var(--grid-height);\n    background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%),\n      linear-gradient(to right, #fff 0%, rgba(255, 255, 255, 0) 100%);\n    border-top-left-radius: var(--sl-border-radius-medium);\n    border-top-right-radius: var(--sl-border-radius-medium);\n    cursor: crosshair;\n    forced-color-adjust: none;\n  }\n\n  .color-picker__grid-handle {\n    position: absolute;\n    width: var(--grid-handle-size);\n    height: var(--grid-handle-size);\n    border-radius: 50%;\n    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);\n    border: solid 2px white;\n    margin-top: calc(var(--grid-handle-size) / -2);\n    margin-left: calc(var(--grid-handle-size) / -2);\n    transition: var(--sl-transition-fast) scale;\n  }\n\n  .color-picker__grid-handle--dragging {\n    cursor: none;\n    scale: 1.5;\n  }\n\n  .color-picker__grid-handle:focus-visible {\n    outline: var(--sl-focus-ring);\n  }\n\n  .color-picker__controls {\n    padding: var(--sl-spacing-small);\n    display: flex;\n    align-items: center;\n  }\n\n  .color-picker__sliders {\n    flex: 1 1 auto;\n  }\n\n  .color-picker__slider {\n    position: relative;\n    height: var(--slider-height);\n    border-radius: var(--sl-border-radius-pill);\n    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);\n    forced-color-adjust: none;\n  }\n\n  .color-picker__slider:not(:last-of-type) {\n    margin-bottom: var(--sl-spacing-small);\n  }\n\n  .color-picker__slider-handle {\n    position: absolute;\n    top: calc(50% - var(--slider-handle-size) / 2);\n    width: var(--slider-handle-size);\n    height: var(--slider-handle-size);\n    background-color: white;\n    border-radius: 50%;\n    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);\n    margin-left: calc(var(--slider-handle-size) / -2);\n  }\n\n  .color-picker__slider-handle:focus-visible {\n    outline: var(--sl-focus-ring);\n  }\n\n  .color-picker__hue {\n    background-image: linear-gradient(\n      to right,\n      rgb(255, 0, 0) 0%,\n      rgb(255, 255, 0) 17%,\n      rgb(0, 255, 0) 33%,\n      rgb(0, 255, 255) 50%,\n      rgb(0, 0, 255) 67%,\n      rgb(255, 0, 255) 83%,\n      rgb(255, 0, 0) 100%\n    );\n  }\n\n  .color-picker__alpha .color-picker__alpha-gradient {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border-radius: inherit;\n  }\n\n  .color-picker__preview {\n    flex: 0 0 auto;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    position: relative;\n    width: 2.25rem;\n    height: 2.25rem;\n    border: none;\n    border-radius: var(--sl-border-radius-circle);\n    background: none;\n    margin-left: var(--sl-spacing-small);\n    cursor: copy;\n    forced-color-adjust: none;\n  }\n\n  .color-picker__preview:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border-radius: inherit;\n    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);\n\n    /* We use a custom property in lieu of currentColor because of https://bugs.webkit.org/show_bug.cgi?id=216780 */\n    background-color: var(--preview-color);\n  }\n\n  .color-picker__preview:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .color-picker__preview-color {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border: solid 1px rgba(0, 0, 0, 0.125);\n  }\n\n  .color-picker__preview-color--copied {\n    animation: pulse 0.75s;\n  }\n\n  @keyframes pulse {\n    0% {\n      box-shadow: 0 0 0 0 var(--sl-color-primary-500);\n    }\n    70% {\n      box-shadow: 0 0 0 0.5rem transparent;\n    }\n    100% {\n      box-shadow: 0 0 0 0 transparent;\n    }\n  }\n\n  .color-picker__user-input {\n    display: flex;\n    padding: 0 var(--sl-spacing-small) var(--sl-spacing-small) var(--sl-spacing-small);\n  }\n\n  .color-picker__user-input sl-input {\n    min-width: 0; /* fix input width in Safari */\n    flex: 1 1 auto;\n  }\n\n  .color-picker__user-input sl-button-group {\n    margin-left: var(--sl-spacing-small);\n  }\n\n  .color-picker__user-input sl-button {\n    min-width: 3.25rem;\n    max-width: 3.25rem;\n    font-size: 1rem;\n  }\n\n  .color-picker__swatches {\n    display: grid;\n    grid-template-columns: repeat(8, 1fr);\n    grid-gap: 0.5rem;\n    justify-items: center;\n    border-top: solid 1px var(--sl-color-neutral-200);\n    padding: var(--sl-spacing-small);\n    forced-color-adjust: none;\n  }\n\n  .color-picker__swatch {\n    position: relative;\n    width: var(--swatch-size);\n    height: var(--swatch-size);\n    border-radius: var(--sl-border-radius-small);\n  }\n\n  .color-picker__swatch .color-picker__swatch-color {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border: solid 1px rgba(0, 0, 0, 0.125);\n    border-radius: inherit;\n    cursor: pointer;\n  }\n\n  .color-picker__swatch:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .color-picker__transparent-bg {\n    background-image: linear-gradient(45deg, var(--sl-color-neutral-300) 25%, transparent 25%),\n      linear-gradient(45deg, transparent 75%, var(--sl-color-neutral-300) 75%),\n      linear-gradient(45deg, transparent 75%, var(--sl-color-neutral-300) 75%),\n      linear-gradient(45deg, var(--sl-color-neutral-300) 25%, transparent 25%);\n    background-size: 10px 10px;\n    background-position:\n      0 0,\n      0 0,\n      -5px -5px,\n      5px 5px;\n  }\n\n  .color-picker--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .color-picker--disabled .color-picker__grid,\n  .color-picker--disabled .color-picker__grid-handle,\n  .color-picker--disabled .color-picker__slider,\n  .color-picker--disabled .color-picker__slider-handle,\n  .color-picker--disabled .color-picker__preview,\n  .color-picker--disabled .color-picker__swatch,\n  .color-picker--disabled .color-picker__swatch-color {\n    pointer-events: none;\n  }\n\n  /*\n   * Color dropdown\n   */\n\n  .color-dropdown::part(panel) {\n    max-height: none;\n    background-color: var(--sl-panel-background-color);\n    border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);\n    border-radius: var(--sl-border-radius-medium);\n    overflow: visible;\n  }\n\n  .color-dropdown__trigger {\n    display: inline-block;\n    position: relative;\n    background-color: transparent;\n    border: none;\n    cursor: pointer;\n    forced-color-adjust: none;\n  }\n\n  .color-dropdown__trigger.color-dropdown__trigger--small {\n    width: var(--sl-input-height-small);\n    height: var(--sl-input-height-small);\n    border-radius: var(--sl-border-radius-circle);\n  }\n\n  .color-dropdown__trigger.color-dropdown__trigger--medium {\n    width: var(--sl-input-height-medium);\n    height: var(--sl-input-height-medium);\n    border-radius: var(--sl-border-radius-circle);\n  }\n\n  .color-dropdown__trigger.color-dropdown__trigger--large {\n    width: var(--sl-input-height-large);\n    height: var(--sl-input-height-large);\n    border-radius: var(--sl-border-radius-circle);\n  }\n\n  .color-dropdown__trigger:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border-radius: inherit;\n    background-color: currentColor;\n    box-shadow:\n      inset 0 0 0 2px var(--sl-input-border-color),\n      inset 0 0 0 4px var(--sl-color-neutral-0);\n  }\n\n  .color-dropdown__trigger--empty:before {\n    background-color: transparent;\n  }\n\n  .color-dropdown__trigger:focus-visible {\n    outline: none;\n  }\n\n  .color-dropdown__trigger:focus-visible:not(.color-dropdown__trigger--disabled) {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .color-dropdown__trigger.color-dropdown__trigger--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n`;\n"
  },
  {
    "path": "src/components/color-picker/color-picker.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport { clickOnElement, dragElement } from '../../internal/test.js';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport { serialize } from '../../utilities/form.js';\nimport sinon from 'sinon';\nimport type SlColorPicker from './color-picker.js';\n\ndescribe('<sl-color-picker>', () => {\n  describe('when the value changes', () => {\n    it('should not emit sl-change or sl-input when the value is changed programmatically', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const color = 'rgb(255, 204, 0)';\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-change should not be emitted'));\n      el.value = color;\n      await el.updateComplete;\n    });\n\n    it('should emit sl-change and sl-input when the color grid selector is moved', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const grid = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"grid\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n\n      // Simulate a drag event. \"sl-change\" should not fire until we stop dragging.\n      await dragElement(grid, 2, 0, {\n        afterMouseDown: () => {\n          expect(changeHandler).to.have.not.been.called;\n          expect(inputHandler).to.have.been.calledOnce;\n        },\n        afterMouseMove: () => {\n          expect(inputHandler).to.have.been.calledTwice;\n        }\n      });\n\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledTwice;\n    });\n\n    it('should emit sl-change and sl-input when the hue slider is moved', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const slider = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"hue-slider\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      // Simulate a drag event. \"sl-change\" should not fire until we stop dragging.\n      await dragElement(slider, 20, 0, {\n        afterMouseDown: () => {\n          expect(changeHandler).to.have.not.been.called;\n          expect(inputHandler).to.have.been.calledOnce;\n        },\n        afterMouseMove: () => {\n          // It's not twice because you can't change the hue of white!\n          expect(inputHandler).to.have.been.calledOnce;\n        }\n      });\n\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      // It's not twice because you can't change the hue of white!\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when the opacity slider is moved', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const slider = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"opacity-slider\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n\n      // Simulate a drag event. \"sl-change\" should not fire until we stop dragging.\n      await dragElement(slider, 2, 0, {\n        afterMouseDown: () => {\n          expect(changeHandler).to.have.not.been.called;\n          expect(inputHandler).to.have.been.calledOnce;\n        },\n        afterMouseMove: () => {\n          expect(inputHandler).to.have.been.calledTwice;\n        }\n      });\n\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledTwice;\n    });\n\n    it('should emit sl-change and sl-input when toggling the format', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker value=\"#fff\"></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const formatButton = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"format-button\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      await clickOnElement(formatButton); // click on the format button\n      await el.updateComplete;\n\n      expect(el.value).to.equal('rgb(255, 255, 255)');\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should render the correct swatches when passing a string of color values', async () => {\n      const el = await fixture<SlColorPicker>(html`\n        <sl-color-picker swatches=\"red; #008000; rgb(0,0,255);\"></sl-color-picker>\n      `);\n      const swatches = [...el.shadowRoot!.querySelectorAll('[part~=\"swatch\"] > div')];\n\n      expect(swatches.length).to.equal(3);\n      expect(getComputedStyle(swatches[0]).backgroundColor).to.equal('rgb(255, 0, 0)');\n      expect(getComputedStyle(swatches[1]).backgroundColor).to.equal('rgb(0, 128, 0)');\n      expect(getComputedStyle(swatches[2]).backgroundColor).to.equal('rgb(0, 0, 255)');\n    });\n\n    it('should render the correct swatches when passing an array of color values', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      el.swatches = ['red', '#008000', 'rgb(0,0,255)'];\n      await el.updateComplete;\n\n      const swatches = [...el.shadowRoot!.querySelectorAll('[part~=\"swatch\"] > div')];\n\n      expect(swatches.length).to.equal(3);\n      expect(getComputedStyle(swatches[0]).backgroundColor).to.equal('rgb(255, 0, 0)');\n      expect(getComputedStyle(swatches[1]).backgroundColor).to.equal('rgb(0, 128, 0)');\n      expect(getComputedStyle(swatches[2]).backgroundColor).to.equal('rgb(0, 0, 255)');\n    });\n\n    it('should emit sl-change and sl-input when clicking on a swatch', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker swatches=\"red; green; blue;\"></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const swatch = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"swatch\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      await clickOnElement(swatch); // click on the swatch\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when selecting a color with the keyboard', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const gridHandle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"grid-handle\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      gridHandle.focus();\n      await sendKeys({ press: 'ArrowRight' }); // move the grid handle\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when selecting a color with the keyboard', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"grid-handle\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      handle.focus();\n      await sendKeys({ press: 'ArrowRight' }); // move the handle\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when selecting hue with the keyboard', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"hue-slider\"] > span')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      handle.focus();\n      await sendKeys({ press: 'ArrowRight' }); // move the handle\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when selecting opacity with the keyboard', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"opacity-slider\"] > span')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      handle.focus();\n      await sendKeys({ press: 'ArrowRight' }); // move the handle\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when entering a value in the color input and pressing enter', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const input = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"input\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      input.focus(); // focus the input\n      await el.updateComplete;\n      await sendKeys({ type: 'fc0' }); // type in a color\n      await sendKeys({ press: 'Enter' }); // press enter\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input when entering a value in the color input and blurring the field', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const input = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"input\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      input.focus(); // focus the input\n      await el.updateComplete;\n      await sendKeys({ type: 'fc0' }); // type in a color\n      input.blur(); // commit changes by blurring the field\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should render the correct format when selecting a swatch of a different format', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker format=\"rgb\"></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      el.swatches = ['#fff'];\n      await el.updateComplete;\n      const swatch = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"swatch\"]')!;\n\n      await clickOnElement(trigger); // open the dropdown\n      await aTimeout(200); // wait for the dropdown to open\n      await clickOnElement(swatch); // click on the swatch\n      await el.updateComplete;\n\n      expect(el.value).to.equal('rgb(255, 255, 255)');\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n  });\n\n  it('should render in a dropdown', async () => {\n    const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n    const dropdown = el.shadowRoot!.querySelector('sl-dropdown');\n\n    expect(dropdown).to.exist;\n  });\n\n  it('should not render in a dropdown when inline is enabled', async () => {\n    const el = await fixture<SlColorPicker>(html` <sl-color-picker inline></sl-color-picker> `);\n    const dropdown = el.shadowRoot!.querySelector('sl-dropdown');\n\n    expect(dropdown).to.not.exist;\n  });\n\n  it('should show opacity slider when opacity is enabled', async () => {\n    const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity></sl-color-picker> `);\n    const opacitySlider = el.shadowRoot!.querySelector('[part*=\"opacity-slider\"]')!;\n\n    expect(opacitySlider).to.exist;\n  });\n\n  it('should display a color when an initial value is provided', async () => {\n    const el = await fixture<SlColorPicker>(html` <sl-color-picker value=\"#000\"></sl-color-picker> `);\n    const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]');\n\n    expect(trigger?.style.color).to.equal('rgb(0, 0, 0)');\n  });\n\n  it('should display a color with opacity when an initial value with opacity is provided', async () => {\n    const el = await fixture<SlColorPicker>(html` <sl-color-picker opacity value=\"#ff000050\"></sl-color-picker> `);\n    const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n    const previewButton = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"preview\"]');\n    const previewColor = getComputedStyle(previewButton!).getPropertyValue('--preview-color');\n\n    expect(trigger.style.color).to.equal('rgba(255, 0, 0, 0.314)');\n    expect(previewColor).to.equal('#ff000050');\n  });\n\n  it.skip('should emit sl-focus when rendered as a dropdown and focused', async () => {\n    const el = await fixture<SlColorPicker>(html`\n      <div>\n        <sl-color-picker></sl-color-picker>\n        <button type=\"button\">Click me</button>\n      </div>\n    `);\n    const colorPicker = el.querySelector('sl-color-picker')!;\n    const trigger = colorPicker.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"trigger\"]')!;\n    const button = el.querySelector('button')!;\n    const focusHandler = sinon.spy();\n    const blurHandler = sinon.spy();\n\n    colorPicker.addEventListener('sl-focus', focusHandler);\n    colorPicker.addEventListener('sl-blur', blurHandler);\n\n    await clickOnElement(trigger);\n    await colorPicker.updateComplete;\n    expect(focusHandler).to.have.been.calledOnce;\n\n    await clickOnElement(button);\n    await colorPicker.updateComplete;\n    expect(blurHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-focus when rendered inline and focused', async () => {\n    const el = await fixture<SlColorPicker>(html`\n      <div>\n        <sl-color-picker inline></sl-color-picker>\n        <button type=\"button\">Click me</button>\n      </div>\n    `);\n    const colorPicker = el.querySelector('sl-color-picker')!;\n    const button = el.querySelector('button')!;\n    const focusHandler = sinon.spy();\n    const blurHandler = sinon.spy();\n\n    colorPicker.addEventListener('sl-focus', focusHandler);\n    colorPicker.addEventListener('sl-blur', blurHandler);\n\n    await clickOnElement(colorPicker);\n    await colorPicker.updateComplete;\n    expect(focusHandler).to.have.been.calledOnce;\n\n    await clickOnElement(button);\n    await colorPicker.updateComplete;\n    expect(blurHandler).to.have.been.calledOnce;\n  });\n\n  it('should focus and blur when calling focus() and blur() and rendered as a dropdown', async () => {\n    const colorPicker = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n    const focusHandler = sinon.spy();\n    const blurHandler = sinon.spy();\n\n    colorPicker.addEventListener('sl-focus', focusHandler);\n    colorPicker.addEventListener('sl-blur', blurHandler);\n\n    // Focus\n    colorPicker.focus();\n    await colorPicker.updateComplete;\n\n    expect(document.activeElement).to.equal(colorPicker);\n    expect(focusHandler).to.have.been.calledOnce;\n\n    // Blur\n    colorPicker.blur();\n    await colorPicker.updateComplete;\n\n    expect(document.activeElement).to.equal(document.body);\n    expect(blurHandler).to.have.been.calledOnce;\n  });\n\n  it('should focus and blur when calling focus() and blur() and rendered inline', async () => {\n    const colorPicker = await fixture<SlColorPicker>(html` <sl-color-picker inline></sl-color-picker> `);\n    const focusHandler = sinon.spy();\n    const blurHandler = sinon.spy();\n\n    colorPicker.addEventListener('sl-focus', focusHandler);\n    colorPicker.addEventListener('sl-blur', blurHandler);\n\n    // Focus\n    colorPicker.focus();\n    await colorPicker.updateComplete;\n\n    expect(document.activeElement).to.equal(colorPicker);\n    expect(focusHandler).to.have.been.calledOnce;\n\n    // Blur\n    colorPicker.blur();\n    await colorPicker.updateComplete;\n\n    expect(document.activeElement).to.equal(document.body);\n    expect(blurHandler).to.have.been.calledOnce;\n  });\n\n  describe('when submitting a form', () => {\n    it('should serialize its name and value with FormData', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-color-picker name=\"a\" value=\"#ffcc00\"></sl-color-picker>\n        </form>\n      `);\n      const formData = new FormData(form);\n      expect(formData.get('a')).to.equal('#ffcc00');\n    });\n\n    it('should serialize its name and value with JSON', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-color-picker name=\"a\" value=\"#ffcc00\"></sl-color-picker>\n        </form>\n      `);\n      const json = serialize(form);\n      expect(json.a).to.equal('#ffcc00');\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-color-picker form=\"f\" name=\"a\" value=\"#ffcc00\"></sl-color-picker>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('#ffcc00');\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-color-picker name=\"a\" value=\"#ffffff\"></sl-color-picker>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const colorPicker = form.querySelector('sl-color-picker')!;\n      colorPicker.value = '#000000';\n\n      await colorPicker.updateComplete;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await colorPicker.updateComplete;\n\n      expect(colorPicker.value).to.equal('#ffffff');\n\n      colorPicker.defaultValue = '';\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await colorPicker.updateComplete;\n\n      expect(colorPicker.value).to.equal('');\n    });\n  });\n\n  describe('when using constraint validation', () => {\n    it('should be valid by default', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker></sl-color-picker> `);\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it('should be invalid when required and empty', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker required></sl-color-picker> `);\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should be invalid when required and disabled is removed', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker disabled required></sl-color-picker> `);\n      el.disabled = false;\n      await el.updateComplete;\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when valid', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker required value=\"#fff\"></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector('[part~=\"trigger\"]')!;\n      const grid = el.shadowRoot!.querySelector('[part~=\"grid\"]')!;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.false;\n      expect(el.hasAttribute('data-valid')).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(trigger);\n      await aTimeout(500);\n      await clickOnElement(grid);\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.true;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when invalid', async () => {\n      const el = await fixture<SlColorPicker>(html` <sl-color-picker required></sl-color-picker> `);\n      const trigger = el.shadowRoot!.querySelector('[part~=\"trigger\"]')!;\n      const grid = el.shadowRoot!.querySelector('[part~=\"grid\"]')!;\n\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.true;\n      expect(el.hasAttribute('data-valid')).to.be.false;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(trigger);\n      await aTimeout(500);\n      await clickOnElement(grid);\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.true;\n    });\n  });\n\n  runFormControlBaseTests('sl-color-picker');\n});\n"
  },
  {
    "path": "src/components/color-picker/color-picker.ts",
    "content": "import SlColorPicker from './color-picker.component.js';\n\nexport * from './color-picker.component.js';\nexport default SlColorPicker;\n\nSlColorPicker.define('sl-color-picker');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-color-picker': SlColorPicker;\n  }\n}\n"
  },
  {
    "path": "src/components/copy-button/copy-button.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlTooltip from '../tooltip/tooltip.component.js';\nimport styles from './copy-button.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Copies text data to the clipboard when the user clicks the trigger.\n * @documentation https://shoelace.style/components/copy\n * @status experimental\n * @since 2.7\n *\n * @dependency sl-icon\n * @dependency sl-tooltip\n *\n * @event sl-copy - Emitted when the data has been copied.\n * @event sl-error - Emitted when the data could not be copied.\n *\n * @slot copy-icon - The icon to show in the default copy state. Works best with `<sl-icon>`.\n * @slot success-icon - The icon to show when the content is copied. Works best with `<sl-icon>`.\n * @slot error-icon - The icon to show when a copy error occurs. Works best with `<sl-icon>`.\n *\n * @csspart button - The internal `<button>` element.\n * @csspart copy-icon - The container that holds the copy icon.\n * @csspart success-icon - The container that holds the success icon.\n * @csspart error-icon - The container that holds the error icon.\n * @csspart tooltip__base - The tooltip's exported `base` part.\n * @csspart tooltip__base__popup - The tooltip's exported `popup` part.\n * @csspart tooltip__base__arrow - The tooltip's exported `arrow` part.\n * @csspart tooltip__body - The tooltip's exported `body` part.\n *\n * @cssproperty --success-color - The color to use for success feedback.\n * @cssproperty --error-color - The color to use for error feedback.\n *\n * @animation copy.in - The animation to use when feedback icons animate in.\n * @animation copy.out - The animation to use when feedback icons animate out.\n */\nexport default class SlCopyButton extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-icon': SlIcon,\n    'sl-tooltip': SlTooltip\n  };\n\n  private readonly localize = new LocalizeController(this);\n\n  @query('slot[name=\"copy-icon\"]') copyIcon: HTMLSlotElement;\n  @query('slot[name=\"success-icon\"]') successIcon: HTMLSlotElement;\n  @query('slot[name=\"error-icon\"]') errorIcon: HTMLSlotElement;\n  @query('sl-tooltip') tooltip: SlTooltip;\n\n  @state() isCopying = false;\n  @state() status: 'rest' | 'success' | 'error' = 'rest';\n\n  /** The text value to copy. */\n  @property() value = '';\n\n  /**\n   * An id that references an element in the same document from which data will be copied. If both this and `value` are\n   * present, this value will take precedence. By default, the target element's `textContent` will be copied. To copy an\n   * attribute, append the attribute name wrapped in square brackets, e.g. `from=\"el[value]\"`. To copy a property,\n   * append a dot and the property name, e.g. `from=\"el.value\"`.\n   */\n  @property() from = '';\n\n  /** Disables the copy button. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** A custom label to show in the tooltip. */\n  @property({ attribute: 'copy-label' }) copyLabel = '';\n\n  /** A custom label to show in the tooltip after copying. */\n  @property({ attribute: 'success-label' }) successLabel = '';\n\n  /** A custom label to show in the tooltip when a copy error occurs. */\n  @property({ attribute: 'error-label' }) errorLabel = '';\n\n  /** The length of time to show feedback before restoring the default trigger. */\n  @property({ attribute: 'feedback-duration', type: Number }) feedbackDuration = 1000;\n\n  /** The preferred placement of the tooltip. */\n  @property({ attribute: 'tooltip-placement' }) tooltipPlacement: 'top' | 'right' | 'bottom' | 'left' = 'top';\n\n  /**\n   * Enable this option to prevent the tooltip from being clipped when the component is placed inside a container with\n   * `overflow: auto|hidden|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all,\n   * scenarios.\n   */\n  @property({ type: Boolean }) hoist = false;\n\n  private async handleCopy() {\n    if (this.disabled || this.isCopying) {\n      return;\n    }\n    this.isCopying = true;\n\n    // Copy the value by default\n    let valueToCopy = this.value;\n\n    // If an element is specified, copy from that instead\n    if (this.from) {\n      const root = this.getRootNode() as ShadowRoot | Document;\n\n      // Simple way to parse ids, properties, and attributes\n      const isProperty = this.from.includes('.');\n      const isAttribute = this.from.includes('[') && this.from.includes(']');\n      let id = this.from;\n      let field = '';\n\n      if (isProperty) {\n        // Split at the dot\n        [id, field] = this.from.trim().split('.');\n      } else if (isAttribute) {\n        // Trim the ] and split at the [\n        [id, field] = this.from.trim().replace(/\\]$/, '').split('[');\n      }\n\n      // Locate the target element by id\n      const target = 'getElementById' in root ? root.getElementById(id) : null;\n\n      if (target) {\n        if (isAttribute) {\n          valueToCopy = target.getAttribute(field) || '';\n        } else if (isProperty) {\n          // @ts-expect-error - deal with it\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n          valueToCopy = target[field] || '';\n        } else {\n          valueToCopy = target.textContent || '';\n        }\n      } else {\n        // No target\n        this.showStatus('error');\n        this.emit('sl-error');\n      }\n    }\n\n    // No value\n    if (!valueToCopy) {\n      this.showStatus('error');\n      this.emit('sl-error');\n    } else {\n      try {\n        await navigator.clipboard.writeText(valueToCopy);\n        this.showStatus('success');\n        this.emit('sl-copy', {\n          detail: {\n            value: valueToCopy\n          }\n        });\n      } catch (error) {\n        // Rejected by browser\n        this.showStatus('error');\n        this.emit('sl-error');\n      }\n    }\n  }\n\n  private async showStatus(status: 'success' | 'error') {\n    const copyLabel = this.copyLabel || this.localize.term('copy');\n    const successLabel = this.successLabel || this.localize.term('copied');\n    const errorLabel = this.errorLabel || this.localize.term('error');\n    const iconToShow = status === 'success' ? this.successIcon : this.errorIcon;\n    const showAnimation = getAnimation(this, 'copy.in', { dir: 'ltr' });\n    const hideAnimation = getAnimation(this, 'copy.out', { dir: 'ltr' });\n\n    this.tooltip.content = status === 'success' ? successLabel : errorLabel;\n\n    // Show the feedback icon\n    await this.copyIcon.animate(hideAnimation.keyframes, hideAnimation.options).finished;\n    this.copyIcon.hidden = true;\n    this.status = status;\n    iconToShow.hidden = false;\n    await iconToShow.animate(showAnimation.keyframes, showAnimation.options).finished;\n\n    // After a brief delay, restore the original state\n    setTimeout(async () => {\n      await iconToShow.animate(hideAnimation.keyframes, hideAnimation.options).finished;\n      iconToShow.hidden = true;\n      this.status = 'rest';\n      this.copyIcon.hidden = false;\n      await this.copyIcon.animate(showAnimation.keyframes, showAnimation.options).finished;\n\n      this.tooltip.content = copyLabel;\n      this.isCopying = false;\n    }, this.feedbackDuration);\n  }\n\n  render() {\n    const copyLabel = this.copyLabel || this.localize.term('copy');\n\n    return html`\n      <sl-tooltip\n        class=${classMap({\n          'copy-button': true,\n          'copy-button--success': this.status === 'success',\n          'copy-button--error': this.status === 'error'\n        })}\n        content=${copyLabel}\n        placement=${this.tooltipPlacement}\n        ?disabled=${this.disabled}\n        ?hoist=${this.hoist}\n        exportparts=\"\n          base:tooltip__base,\n          base__popup:tooltip__base__popup,\n          base__arrow:tooltip__base__arrow,\n          body:tooltip__body\n        \"\n      >\n        <button\n          class=\"copy-button__button\"\n          part=\"button\"\n          type=\"button\"\n          ?disabled=${this.disabled}\n          @click=${this.handleCopy}\n        >\n          <slot part=\"copy-icon\" name=\"copy-icon\">\n            <sl-icon library=\"system\" name=\"copy\"></sl-icon>\n          </slot>\n          <slot part=\"success-icon\" name=\"success-icon\" hidden>\n            <sl-icon library=\"system\" name=\"check\"></sl-icon>\n          </slot>\n          <slot part=\"error-icon\" name=\"error-icon\" hidden>\n            <sl-icon library=\"system\" name=\"x-lg\"></sl-icon>\n          </slot>\n        </button>\n      </sl-tooltip>\n    `;\n  }\n}\n\nsetDefaultAnimation('copy.in', {\n  keyframes: [\n    { scale: '.25', opacity: '.25' },\n    { scale: '1', opacity: '1' }\n  ],\n  options: { duration: 100 }\n});\n\nsetDefaultAnimation('copy.out', {\n  keyframes: [\n    { scale: '1', opacity: '1' },\n    { scale: '.25', opacity: '0' }\n  ],\n  options: { duration: 100 }\n});\n"
  },
  {
    "path": "src/components/copy-button/copy-button.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --error-color: var(--sl-color-danger-600);\n    --success-color: var(--sl-color-success-600);\n\n    display: inline-block;\n  }\n\n  .copy-button__button {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    background: none;\n    border: none;\n    border-radius: var(--sl-border-radius-medium);\n    font-size: inherit;\n    color: inherit;\n    padding: var(--sl-spacing-x-small);\n    cursor: pointer;\n    transition: var(--sl-transition-x-fast) color;\n  }\n\n  .copy-button--success .copy-button__button {\n    color: var(--success-color);\n  }\n\n  .copy-button--error .copy-button__button {\n    color: var(--error-color);\n  }\n\n  .copy-button__button:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .copy-button__button[disabled] {\n    opacity: 0.5;\n    cursor: not-allowed !important;\n  }\n\n  slot {\n    display: inline-flex;\n  }\n`;\n"
  },
  {
    "path": "src/components/copy-button/copy-button.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlCopyButton from './copy-button.js';\n\n// We use aria-live to announce labels via tooltips\nconst ignoredRules = ['button-name'];\n\ndescribe('<sl-copy-button>', () => {\n  let el: SlCopyButton;\n\n  describe('when provided no parameters', () => {\n    before(async () => {\n      el = await fixture(html`<sl-copy-button value=\"something\"></sl-copy-button> `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible({ ignoredRules });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/copy-button/copy-button.ts",
    "content": "import SlCopyButton from './copy-button.component.js';\n\nexport * from './copy-button.component.js';\nexport default SlCopyButton;\n\nSlCopyButton.define('sl-copy-button');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-copy-button': SlCopyButton;\n  }\n}\n"
  },
  {
    "path": "src/components/details/details.component.ts",
    "content": "import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../internal/animate.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './details.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Details show a brief summary and expand to show additional content.\n * @documentation https://shoelace.style/components/details\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot - The details' main content.\n * @slot summary - The details' summary. Alternatively, you can use the `summary` attribute.\n * @slot expand-icon - Optional expand icon to use instead of the default. Works best with `<sl-icon>`.\n * @slot collapse-icon - Optional collapse icon to use instead of the default. Works best with `<sl-icon>`.\n *\n * @event sl-show - Emitted when the details opens.\n * @event sl-after-show - Emitted after the details opens and all animations are complete.\n * @event sl-hide - Emitted when the details closes.\n * @event sl-after-hide - Emitted after the details closes and all animations are complete.\n *\n * @csspart base - The component's base wrapper.\n * @csspart header - The header that wraps both the summary and the expand/collapse icon.\n * @csspart summary - The container that wraps the summary.\n * @csspart summary-icon - The container that wraps the expand/collapse icons.\n * @csspart content - The details content.\n *\n * @animation details.show - The animation to use when showing details. You can use `height: auto` with this animation.\n * @animation details.hide - The animation to use when hiding details. You can use `height: auto` with this animation.\n */\nexport default class SlDetails extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  static dependencies = {\n    'sl-icon': SlIcon\n  };\n\n  private readonly localize = new LocalizeController(this);\n\n  @query('.details') details: HTMLDetailsElement;\n  @query('.details__header') header: HTMLElement;\n  @query('.details__body') body: HTMLElement;\n  @query('.details__expand-icon-slot') expandIconSlot: HTMLSlotElement;\n\n  detailsObserver: MutationObserver;\n\n  /**\n   * Indicates whether or not the details is open. You can toggle this attribute to show and hide the details, or you\n   * can use the `show()` and `hide()` methods and this attribute will reflect the details' open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /** The summary to show in the header. If you need to display HTML, use the `summary` slot instead. */\n  @property() summary: string;\n\n  /** Disables the details so it can't be toggled. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  firstUpdated() {\n    this.body.style.height = this.open ? 'auto' : '0';\n    if (this.open) {\n      this.details.open = true;\n    }\n\n    this.detailsObserver = new MutationObserver(changes => {\n      for (const change of changes) {\n        if (change.type === 'attributes' && change.attributeName === 'open') {\n          if (this.details.open) {\n            this.show();\n          } else {\n            this.hide();\n          }\n        }\n      }\n    });\n    this.detailsObserver.observe(this.details, { attributes: true });\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.detailsObserver?.disconnect();\n  }\n\n  private handleSummaryClick(event: MouseEvent) {\n    event.preventDefault();\n\n    if (!this.disabled) {\n      if (this.open) {\n        this.hide();\n      } else {\n        this.show();\n      }\n      this.header.focus();\n    }\n  }\n\n  private handleSummaryKeyDown(event: KeyboardEvent) {\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n\n      if (this.open) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n\n    if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n      event.preventDefault();\n      this.hide();\n    }\n\n    if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n      event.preventDefault();\n      this.show();\n    }\n  }\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open) {\n      this.details.open = true;\n      // Show\n      const slShow = this.emit('sl-show', { cancelable: true });\n      if (slShow.defaultPrevented) {\n        this.open = false;\n        this.details.open = false;\n        return;\n      }\n\n      await stopAnimations(this.body);\n\n      const { keyframes, options } = getAnimation(this, 'details.show', { dir: this.localize.dir() });\n      await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);\n      this.body.style.height = 'auto';\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      const slHide = this.emit('sl-hide', { cancelable: true });\n      if (slHide.defaultPrevented) {\n        this.details.open = true;\n        this.open = true;\n        return;\n      }\n\n      await stopAnimations(this.body);\n\n      const { keyframes, options } = getAnimation(this, 'details.hide', { dir: this.localize.dir() });\n      await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);\n      this.body.style.height = 'auto';\n\n      this.details.open = false;\n      this.emit('sl-after-hide');\n    }\n  }\n\n  /** Shows the details. */\n  async show() {\n    if (this.open || this.disabled) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the details */\n  async hide() {\n    if (!this.open || this.disabled) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n\n    return html`\n      <details\n        part=\"base\"\n        class=${classMap({\n          details: true,\n          'details--open': this.open,\n          'details--disabled': this.disabled,\n          'details--rtl': isRtl\n        })}\n      >\n        <summary\n          part=\"header\"\n          id=\"header\"\n          class=\"details__header\"\n          role=\"button\"\n          aria-expanded=${this.open ? 'true' : 'false'}\n          aria-controls=\"content\"\n          aria-disabled=${this.disabled ? 'true' : 'false'}\n          tabindex=${this.disabled ? '-1' : '0'}\n          @click=${this.handleSummaryClick}\n          @keydown=${this.handleSummaryKeyDown}\n        >\n          <slot name=\"summary\" part=\"summary\" class=\"details__summary\">${this.summary}</slot>\n\n          <span part=\"summary-icon\" class=\"details__summary-icon\">\n            <slot name=\"expand-icon\">\n              <sl-icon library=\"system\" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>\n            </slot>\n            <slot name=\"collapse-icon\">\n              <sl-icon library=\"system\" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>\n            </slot>\n          </span>\n        </summary>\n\n        <div class=\"details__body\" role=\"region\" aria-labelledby=\"header\">\n          <slot part=\"content\" id=\"content\" class=\"details__content\"></slot>\n        </div>\n      </details>\n    `;\n  }\n}\n\nsetDefaultAnimation('details.show', {\n  keyframes: [\n    { height: '0', opacity: '0' },\n    { height: 'auto', opacity: '1' }\n  ],\n  options: { duration: 250, easing: 'linear' }\n});\n\nsetDefaultAnimation('details.hide', {\n  keyframes: [\n    { height: 'auto', opacity: '1' },\n    { height: '0', opacity: '0' }\n  ],\n  options: { duration: 250, easing: 'linear' }\n});\n"
  },
  {
    "path": "src/components/details/details.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  .details {\n    border: solid 1px var(--sl-color-neutral-200);\n    border-radius: var(--sl-border-radius-medium);\n    background-color: var(--sl-color-neutral-0);\n    overflow-anchor: none;\n  }\n\n  .details--disabled {\n    opacity: 0.5;\n  }\n\n  .details__header {\n    display: flex;\n    align-items: center;\n    border-radius: inherit;\n    padding: var(--sl-spacing-medium);\n    user-select: none;\n    -webkit-user-select: none;\n    cursor: pointer;\n  }\n\n  .details__header::-webkit-details-marker {\n    display: none;\n  }\n\n  .details__header:focus {\n    outline: none;\n  }\n\n  .details__header:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: calc(1px + var(--sl-focus-ring-offset));\n  }\n\n  .details--disabled .details__header {\n    cursor: not-allowed;\n  }\n\n  .details--disabled .details__header:focus-visible {\n    outline: none;\n    box-shadow: none;\n  }\n\n  .details__summary {\n    flex: 1 1 auto;\n    display: flex;\n    align-items: center;\n  }\n\n  .details__summary-icon {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    transition: var(--sl-transition-medium) rotate ease;\n  }\n\n  .details--open .details__summary-icon {\n    rotate: 90deg;\n  }\n\n  .details--open.details--rtl .details__summary-icon {\n    rotate: -90deg;\n  }\n\n  .details--open slot[name='expand-icon'],\n  .details:not(.details--open) slot[name='collapse-icon'] {\n    display: none;\n  }\n\n  .details__body {\n    overflow: hidden;\n  }\n\n  .details__content {\n    display: block;\n    padding: var(--sl-spacing-medium);\n  }\n`;\n"
  },
  {
    "path": "src/components/details/details.test.ts",
    "content": "import '../../../dist/shoelace.js';\n// cspell:dictionaries lorem-ipsum\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type { SlHideEvent } from '../../events/sl-hide.js';\nimport type { SlShowEvent } from '../../events/sl-show.js';\nimport type SlDetails from './details.js';\n\ndescribe('<sl-details>', () => {\n  describe('accessibility', () => {\n    it('should be accessible when closed', async () => {\n      const details = await fixture<SlDetails>(html`<sl-details summary=\"Test\"> Test text </sl-details>`);\n\n      await expect(details).to.be.accessible();\n    });\n\n    it('should be accessible when open', async () => {\n      const details = await fixture<SlDetails>(html`<sl-details open summary=\"Test\">Test text</sl-details>`);\n\n      await expect(details).to.be.accessible();\n    });\n  });\n\n  it('should be visible with the open attribute', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details open>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;\n\n    expect(parseInt(getComputedStyle(body).height)).to.be.greaterThan(0);\n  });\n\n  it('should not be visible without the open attribute', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details summary=\"click me\">\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;\n    expect(parseInt(getComputedStyle(body).height)).to.equal(0);\n  });\n\n  it('should emit sl-show and sl-after-show when calling show()', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.show();\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details open>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.hide();\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-show and sl-after-show when setting open = true', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details open>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n  });\n\n  it('should not open when preventing sl-show', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const showHandler = sinon.spy((event: SlShowEvent) => event.preventDefault());\n\n    el.addEventListener('sl-show', showHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(el.open).to.be.false;\n  });\n\n  it('should not close when preventing sl-hide', async () => {\n    const el = await fixture<SlDetails>(html`\n      <sl-details open>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\n        magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n        consequat.\n      </sl-details>\n    `);\n    const hideHandler = sinon.spy((event: SlHideEvent) => event.preventDefault());\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(el.open).to.be.true;\n  });\n\n  it('should be the correct size after opening more than one instance', async () => {\n    const el = await fixture<SlDetails>(html`\n      <div>\n        <sl-details>\n          <div style=\"height: 200px;\"></div>\n        </sl-details>\n        <sl-details>\n          <div style=\"height: 400px;\"></div>\n        </sl-details>\n      </div>\n    `);\n    const first = el.querySelectorAll('sl-details')[0];\n    const second = el.querySelectorAll('sl-details')[1];\n    const firstBody = first.shadowRoot!.querySelector('.details__body')!;\n    const secondBody = second.shadowRoot!.querySelector('.details__body')!;\n\n    await first.show();\n    await second.show();\n\n    expect(firstBody.clientHeight).to.equal(232); // 200 + 16px + 16px (vertical padding)\n    expect(secondBody.clientHeight).to.equal(432); // 400 + 16px + 16px (vertical padding)\n  });\n});\n"
  },
  {
    "path": "src/components/details/details.ts",
    "content": "import SlDetails from './details.component.js';\n\nexport * from './details.component.js';\nexport default SlDetails;\n\nSlDetails.define('sl-details');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-details': SlDetails;\n  }\n}\n"
  },
  {
    "path": "src/components/dialog/dialog.component.ts",
    "content": "import { animateTo, stopAnimations } from '../../internal/animate.js';\nimport { blurActiveElement } from '../../internal/closeActiveElement.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';\nimport { property, query } from 'lit/decorators.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport Modal from '../../internal/modal.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport styles from './dialog.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Dialogs, sometimes called \"modals\", appear above the page and require the user's immediate attention.\n * @documentation https://shoelace.style/components/dialog\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - The dialog's main content.\n * @slot label - The dialog's label. Alternatively, you can use the `label` attribute.\n * @slot header-actions - Optional actions to add to the header. Works best with `<sl-icon-button>`.\n * @slot footer - The dialog's footer, usually one or more buttons representing various options.\n *\n * @event sl-show - Emitted when the dialog opens.\n * @event sl-after-show - Emitted after the dialog opens and all animations are complete.\n * @event sl-hide - Emitted when the dialog closes.\n * @event sl-after-hide - Emitted after the dialog closes and all animations are complete.\n * @event sl-initial-focus - Emitted when the dialog opens and is ready to receive focus. Calling\n *   `event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input.\n * @event {{ source: 'close-button' | 'keyboard' | 'overlay' }} sl-request-close - Emitted when the user attempts to\n *   close the dialog by clicking the close button, clicking the overlay, or pressing escape. Calling\n *   `event.preventDefault()` will keep the dialog open. Avoid using this unless closing the dialog will result in\n *   destructive behavior such as data loss.\n *\n * @csspart base - The component's base wrapper.\n * @csspart overlay - The overlay that covers the screen behind the dialog.\n * @csspart panel - The dialog's panel (where the dialog and its content are rendered).\n * @csspart header - The dialog's header. This element wraps the title and header actions.\n * @csspart header-actions - Optional actions to add to the header. Works best with `<sl-icon-button>`.\n * @csspart title - The dialog's title.\n * @csspart close-button - The close button, an `<sl-icon-button>`.\n * @csspart close-button__base - The close button's exported `base` part.\n * @csspart body - The dialog's body.\n * @csspart footer - The dialog's footer.\n *\n * @cssproperty --width - The preferred width of the dialog. Note that the dialog will shrink to accommodate smaller screens.\n * @cssproperty --header-spacing - The amount of padding to use for the header.\n * @cssproperty --body-spacing - The amount of padding to use for the body.\n * @cssproperty --footer-spacing - The amount of padding to use for the footer.\n *\n * @animation dialog.show - The animation to use when showing the dialog.\n * @animation dialog.hide - The animation to use when hiding the dialog.\n * @animation dialog.denyClose - The animation to use when a request to close the dialog is denied.\n * @animation dialog.overlay.show - The animation to use when showing the dialog's overlay.\n * @animation dialog.overlay.hide - The animation to use when hiding the dialog's overlay.\n *\n * @property modal - Exposes the internal modal utility that controls focus trapping. To temporarily disable focus\n *   trapping and allow third-party modals spawned from an active Shoelace modal, call `modal.activateExternal()` when\n *   the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.\n */\nexport default class SlDialog extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-icon-button': SlIconButton\n  };\n\n  private readonly hasSlotController = new HasSlotController(this, 'footer');\n  private readonly localize = new LocalizeController(this);\n  private originalTrigger: HTMLElement | null;\n  public modal = new Modal(this);\n  private closeWatcher: CloseWatcher | null;\n\n  @query('.dialog') dialog: HTMLElement;\n  @query('.dialog__panel') panel: HTMLElement;\n  @query('.dialog__overlay') overlay: HTMLElement;\n\n  /**\n   * Indicates whether or not the dialog is open. You can toggle this attribute to show and hide the dialog, or you can\n   * use the `show()` and `hide()` methods and this attribute will reflect the dialog's open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /**\n   * The dialog's label as displayed in the header. You should always include a relevant label even when using\n   * `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.\n   */\n  @property({ reflect: true }) label = '';\n\n  /**\n   * Disables the header. This will also remove the default close button, so please ensure you provide an easy,\n   * accessible way for users to dismiss the dialog.\n   */\n  @property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;\n\n  firstUpdated() {\n    this.dialog.hidden = !this.open;\n\n    if (this.open) {\n      this.addOpenListeners();\n      this.modal.activate();\n      lockBodyScrolling(this);\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.modal.deactivate();\n    unlockBodyScrolling(this);\n    this.removeOpenListeners();\n  }\n\n  private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {\n    const slRequestClose = this.emit('sl-request-close', {\n      cancelable: true,\n      detail: { source }\n    });\n\n    if (slRequestClose.defaultPrevented) {\n      const animation = getAnimation(this, 'dialog.denyClose', { dir: this.localize.dir() });\n      animateTo(this.panel, animation.keyframes, animation.options);\n      return;\n    }\n\n    this.hide();\n  }\n\n  private addOpenListeners() {\n    if ('CloseWatcher' in window) {\n      this.closeWatcher?.destroy();\n      this.closeWatcher = new CloseWatcher();\n      this.closeWatcher.onclose = () => this.requestClose('keyboard');\n    } else {\n      document.addEventListener('keydown', this.handleDocumentKeyDown);\n    }\n  }\n\n  private removeOpenListeners() {\n    this.closeWatcher?.destroy();\n    document.removeEventListener('keydown', this.handleDocumentKeyDown);\n  }\n\n  private handleDocumentKeyDown = (event: KeyboardEvent) => {\n    if (event.key === 'Escape' && this.modal.isActive() && this.open) {\n      event.stopPropagation();\n      this.requestClose('keyboard');\n    }\n  };\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open) {\n      // Show\n      this.emit('sl-show');\n      this.addOpenListeners();\n      this.originalTrigger = document.activeElement as HTMLElement;\n      this.modal.activate();\n\n      lockBodyScrolling(this);\n\n      // When the dialog is shown, Safari will attempt to set focus on whatever element has autofocus. This can cause\n      // the dialogs's animation to jitter (if it starts offscreen), so we'll temporarily remove the attribute, call\n      // `focus({ preventScroll: true })` ourselves, and add the attribute back afterwards.\n      //\n      // Related: https://github.com/shoelace-style/shoelace/issues/693\n      //\n      const autoFocusTarget = this.querySelector('[autofocus]');\n      if (autoFocusTarget) {\n        autoFocusTarget.removeAttribute('autofocus');\n      }\n\n      await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);\n      this.dialog.hidden = false;\n\n      // Set initial focus\n      requestAnimationFrame(() => {\n        const slInitialFocus = this.emit('sl-initial-focus', { cancelable: true });\n\n        if (!slInitialFocus.defaultPrevented) {\n          // Set focus to the autofocus target and restore the attribute\n          if (autoFocusTarget) {\n            (autoFocusTarget as HTMLInputElement).focus({ preventScroll: true });\n          } else {\n            this.panel.focus({ preventScroll: true });\n          }\n        }\n\n        // Restore the autofocus attribute\n        if (autoFocusTarget) {\n          autoFocusTarget.setAttribute('autofocus', '');\n        }\n      });\n\n      const panelAnimation = getAnimation(this, 'dialog.show', { dir: this.localize.dir() });\n      const overlayAnimation = getAnimation(this, 'dialog.overlay.show', { dir: this.localize.dir() });\n      await Promise.all([\n        animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),\n        animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)\n      ]);\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      blurActiveElement(this);\n      this.emit('sl-hide');\n      this.removeOpenListeners();\n      this.modal.deactivate();\n\n      await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);\n      const panelAnimation = getAnimation(this, 'dialog.hide', { dir: this.localize.dir() });\n      const overlayAnimation = getAnimation(this, 'dialog.overlay.hide', { dir: this.localize.dir() });\n\n      // Animate the overlay and the panel at the same time. Because animation durations might be different, we need to\n      // hide each one individually when the animation finishes, otherwise the first one that finishes will reappear\n      // unexpectedly. We'll unhide them after all animations have completed.\n      await Promise.all([\n        animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options).then(() => {\n          this.overlay.hidden = true;\n        }),\n        animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options).then(() => {\n          this.panel.hidden = true;\n        })\n      ]);\n\n      this.dialog.hidden = true;\n\n      // Now that the dialog is hidden, restore the overlay and panel for next time\n      this.overlay.hidden = false;\n      this.panel.hidden = false;\n\n      unlockBodyScrolling(this);\n\n      // Restore focus to the original trigger\n      const trigger = this.originalTrigger;\n      if (typeof trigger?.focus === 'function') {\n        setTimeout(() => trigger.focus());\n      }\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  /** Shows the dialog. */\n  async show() {\n    if (this.open) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the dialog */\n  async hide() {\n    if (!this.open) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          dialog: true,\n          'dialog--open': this.open,\n          'dialog--has-footer': this.hasSlotController.test('footer')\n        })}\n      >\n        <div part=\"overlay\" class=\"dialog__overlay\" @click=${() => this.requestClose('overlay')} tabindex=\"-1\"></div>\n\n        <div\n          part=\"panel\"\n          class=\"dialog__panel\"\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-hidden=${this.open ? 'false' : 'true'}\n          aria-label=${ifDefined(this.noHeader ? this.label : undefined)}\n          aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}\n          tabindex=\"-1\"\n        >\n          ${!this.noHeader\n            ? html`\n                <header part=\"header\" class=\"dialog__header\">\n                  <h2 part=\"title\" class=\"dialog__title\" id=\"title\">\n                    <slot name=\"label\"> ${this.label.length > 0 ? this.label : String.fromCharCode(65279)} </slot>\n                  </h2>\n                  <div part=\"header-actions\" class=\"dialog__header-actions\">\n                    <slot name=\"header-actions\"></slot>\n                    <sl-icon-button\n                      part=\"close-button\"\n                      exportparts=\"base:close-button__base\"\n                      class=\"dialog__close\"\n                      name=\"x-lg\"\n                      label=${this.localize.term('close')}\n                      library=\"system\"\n                      @click=\"${() => this.requestClose('close-button')}\"\n                    ></sl-icon-button>\n                  </div>\n                </header>\n              `\n            : ''}\n          ${\n            '' /* The tabindex=\"-1\" is here because the body is technically scrollable if overflowing. However, if there's no focusable elements inside, you won't actually be able to scroll it via keyboard. Previously this was just a <slot>, but tabindex=\"-1\" on the slot causes children to not be focusable. https://github.com/shoelace-style/shoelace/issues/1753#issuecomment-1836803277 */\n          }\n          <div part=\"body\" class=\"dialog__body\" tabindex=\"-1\"><slot></slot></div>\n\n          <footer part=\"footer\" class=\"dialog__footer\">\n            <slot name=\"footer\"></slot>\n          </footer>\n        </div>\n      </div>\n    `;\n  }\n}\n\nsetDefaultAnimation('dialog.show', {\n  keyframes: [\n    { opacity: 0, scale: 0.8 },\n    { opacity: 1, scale: 1 }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('dialog.hide', {\n  keyframes: [\n    { opacity: 1, scale: 1 },\n    { opacity: 0, scale: 0.8 }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('dialog.denyClose', {\n  keyframes: [{ scale: 1 }, { scale: 1.02 }, { scale: 1 }],\n  options: { duration: 250 }\n});\n\nsetDefaultAnimation('dialog.overlay.show', {\n  keyframes: [{ opacity: 0 }, { opacity: 1 }],\n  options: { duration: 250 }\n});\n\nsetDefaultAnimation('dialog.overlay.hide', {\n  keyframes: [{ opacity: 1 }, { opacity: 0 }],\n  options: { duration: 250 }\n});\n"
  },
  {
    "path": "src/components/dialog/dialog.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --width: 31rem;\n    --header-spacing: var(--sl-spacing-large);\n    --body-spacing: var(--sl-spacing-large);\n    --footer-spacing: var(--sl-spacing-large);\n\n    display: contents;\n  }\n\n  .dialog {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    z-index: var(--sl-z-index-dialog);\n  }\n\n  .dialog__panel {\n    display: flex;\n    flex-direction: column;\n    z-index: 2;\n    width: var(--width);\n    max-width: calc(100% - var(--sl-spacing-2x-large));\n    max-height: calc(100% - var(--sl-spacing-2x-large));\n    background-color: var(--sl-panel-background-color);\n    border-radius: var(--sl-border-radius-medium);\n    box-shadow: var(--sl-shadow-x-large);\n  }\n\n  .dialog__panel:focus {\n    outline: none;\n  }\n\n  /* Ensure there's enough vertical padding for phones that don't update vh when chrome appears (e.g. iPhone) */\n  @media screen and (max-width: 420px) {\n    .dialog__panel {\n      max-height: 80vh;\n    }\n  }\n\n  .dialog--open .dialog__panel {\n    display: flex;\n    opacity: 1;\n  }\n\n  .dialog__header {\n    flex: 0 0 auto;\n    display: flex;\n  }\n\n  .dialog__title {\n    flex: 1 1 auto;\n    font: inherit;\n    font-size: var(--sl-font-size-large);\n    line-height: var(--sl-line-height-dense);\n    padding: var(--header-spacing);\n    margin: 0;\n  }\n\n  .dialog__header-actions {\n    flex-shrink: 0;\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: end;\n    gap: var(--sl-spacing-2x-small);\n    padding: 0 var(--header-spacing);\n  }\n\n  .dialog__header-actions sl-icon-button,\n  .dialog__header-actions ::slotted(sl-icon-button) {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    font-size: var(--sl-font-size-medium);\n  }\n\n  .dialog__body {\n    flex: 1 1 auto;\n    display: block;\n    padding: var(--body-spacing);\n    overflow: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n\n  .dialog__footer {\n    flex: 0 0 auto;\n    text-align: right;\n    padding: var(--footer-spacing);\n  }\n\n  .dialog__footer ::slotted(sl-button:not(:first-of-type)) {\n    margin-inline-start: var(--sl-spacing-x-small);\n  }\n\n  .dialog:not(.dialog--has-footer) .dialog__footer {\n    display: none;\n  }\n\n  .dialog__overlay {\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    background-color: var(--sl-overlay-background-color);\n  }\n\n  @media (forced-colors: active) {\n    .dialog__panel {\n      border: solid 1px var(--sl-color-neutral-0);\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/dialog/dialog.test.ts",
    "content": "import '../../../dist/shoelace.js';\n// cspell:dictionaries lorem-ipsum\nimport { aTimeout, elementUpdated, expect, fixture, waitUntil } from '@open-wc/testing';\nimport { html, LitElement } from 'lit';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlDialog from './dialog.js';\n\ndescribe('<sl-dialog>', () => {\n  it('should be visible with the open attribute', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should not be visible without the open attribute', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when calling show()', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.show();\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.hide();\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when setting open = true', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should not close when sl-request-close is prevented', async () => {\n    const el = await fixture<SlDialog>(html`\n      <sl-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-dialog>\n    `);\n    const overlay = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"overlay\"]')!;\n\n    el.addEventListener('sl-request-close', event => {\n      event.preventDefault();\n    });\n    overlay.click();\n\n    expect(el.open).to.be.true;\n  });\n\n  it('should allow initial focus to be set', async () => {\n    const el = await fixture<SlDialog>(html` <sl-dialog><input /></sl-dialog> `);\n    const input = el.querySelector('input')!;\n    const initialFocusHandler = sinon.spy((event: Event) => {\n      event.preventDefault();\n      input.focus();\n    });\n\n    el.addEventListener('sl-initial-focus', initialFocusHandler);\n    el.show();\n\n    await waitUntil(() => initialFocusHandler.calledOnce);\n\n    expect(initialFocusHandler).to.have.been.calledOnce;\n    expect(document.activeElement).to.equal(input);\n  });\n\n  it('should close when pressing Escape', async () => {\n    const el = await fixture<SlDialog>(html` <sl-dialog open></sl-dialog> `);\n    const hideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n\n    await sendKeys({ press: 'Escape' });\n    await waitUntil(() => hideHandler.calledOnce);\n\n    expect(el.open).to.be.false;\n  });\n\n  // https://github.com/shoelace-style/shoelace/issues/1382\n  it('should properly cycle through tabbable elements when sl-dialog is used in a shadowRoot', async () => {\n    class AContainer extends LitElement {\n      get dialog() {\n        return this.shadowRoot?.querySelector('sl-dialog');\n      }\n\n      openDialog() {\n        this.dialog?.show();\n      }\n\n      render() {\n        return html`\n          <h1>Dialog Example</h1>\n          <sl-dialog label=\"Dialog\" class=\"dialog-overview\">\n            Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n            <br />\n            <label><input type=\"checkbox\" />A</label>\n            <label><input type=\"checkbox\" />B</label>\n            <button>Button</button>\n          </sl-dialog>\n\n          <sl-button @click=${this.openDialog}>Open Dialog</sl-button>\n        `;\n      }\n    }\n\n    if (!window.customElements.get('a-container')) {\n      window.customElements.define('a-container', AContainer);\n    }\n\n    const testCase = await fixture(html`\n      <div>\n        <a-container></a-container>\n\n        <p>\n          Open the dialog, then use <kbd>Tab</kbd> to cycle through the inputs. Focus should be trapped, but it reaches\n          things outside the dialog.\n        </p>\n      </div>\n    `);\n\n    const container = testCase.querySelector('a-container');\n\n    if (!container) {\n      throw Error('Could not find <a-container> element.');\n    }\n\n    await elementUpdated(container);\n    const dialog = container.shadowRoot?.querySelector('sl-dialog');\n\n    if (!dialog) {\n      throw Error('Could not find <sl-dialog> element.');\n    }\n\n    const closeButton = dialog.shadowRoot?.querySelector('sl-icon-button');\n    const checkbox1 = dialog.querySelector(\"input[type='checkbox']\");\n    const checkbox2 = dialog.querySelectorAll(\"input[type='checkbox']\")[1];\n    const button = dialog.querySelector('button');\n\n    // Opens modal.\n    const openModalButton = container.shadowRoot?.querySelector('sl-button');\n\n    openModalButton!.click();\n\n    // Test tab cycling\n    await pressTab();\n\n    expect(container.shadowRoot?.activeElement).to.equal(dialog);\n    expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(checkbox1);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(checkbox2);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(button);\n\n    await pressTab();\n    expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(checkbox1);\n\n    // Test Shift+Tab cycling\n\n    // I found these timeouts were needed for WebKit locally.\n    await aTimeout(10);\n    await sendKeys({ down: 'Shift' });\n    await aTimeout(10);\n\n    await pressTab();\n    expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(button);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(checkbox2);\n\n    await pressTab();\n    expect(container.shadowRoot?.activeElement).to.equal(checkbox1);\n\n    await pressTab();\n    expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);\n\n    // End shift+tab cycling\n    await sendKeys({ up: 'Shift' });\n  });\n});\n\n// We wait 50ms just to give the browser some time to figure out the current focus.\n// 50 was the magic number I found locally :shrug:\nasync function pressTab() {\n  await aTimeout(50);\n  await sendKeys({ press: 'Tab' });\n  await aTimeout(50);\n}\n"
  },
  {
    "path": "src/components/dialog/dialog.ts",
    "content": "import SlDialog from './dialog.component.js';\n\nexport * from './dialog.component.js';\nexport default SlDialog;\n\nSlDialog.define('sl-dialog');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-dialog': SlDialog;\n  }\n}\n"
  },
  {
    "path": "src/components/divider/divider.component.ts",
    "content": "import { property } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './divider.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Dividers are used to visually separate or group elements.\n * @documentation https://shoelace.style/components/divider\n * @status stable\n * @since 2.0\n *\n * @cssproperty --color - The color of the divider.\n * @cssproperty --width - The width of the divider.\n * @cssproperty --spacing - The spacing of the divider.\n */\nexport default class SlDivider extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  /** Draws the divider in a vertical orientation. */\n  @property({ type: Boolean, reflect: true }) vertical = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setAttribute('role', 'separator');\n  }\n\n  @watch('vertical')\n  handleVerticalChange() {\n    this.setAttribute('aria-orientation', this.vertical ? 'vertical' : 'horizontal');\n  }\n}\n"
  },
  {
    "path": "src/components/divider/divider.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --color: var(--sl-panel-border-color);\n    --width: var(--sl-panel-border-width);\n    --spacing: var(--sl-spacing-medium);\n  }\n\n  :host(:not([vertical])) {\n    display: block;\n    border-top: solid var(--width) var(--color);\n    margin: var(--spacing) 0;\n  }\n\n  :host([vertical]) {\n    display: inline-block;\n    height: 100%;\n    border-left: solid var(--width) var(--color);\n    margin: 0 var(--spacing);\n  }\n`;\n"
  },
  {
    "path": "src/components/divider/divider.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { elementUpdated, expect, fixture, html } from '@open-wc/testing';\nimport type SlDivider from './divider.js';\n\ndescribe('<sl-divider>', () => {\n  describe('defaults ', () => {\n    it('passes accessibility test', async () => {\n      const el = await fixture<SlDivider>(html` <sl-divider></sl-divider> `);\n      await expect(el).to.be.accessible();\n    });\n\n    it('default properties', async () => {\n      const el = await fixture<SlDivider>(html` <sl-divider></sl-divider> `);\n\n      expect(el.vertical).to.be.false;\n      expect(el.getAttribute('role')).to.equal('separator');\n      expect(el.getAttribute('aria-orientation')).to.equal('horizontal');\n    });\n  });\n\n  describe('vertical property change ', () => {\n    it('aria-orientation is updated', async () => {\n      const el = await fixture<SlDivider>(html` <sl-divider></sl-divider> `);\n\n      el.vertical = true;\n      await elementUpdated(el);\n\n      expect(el.getAttribute('aria-orientation')).to.equal('vertical');\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/divider/divider.ts",
    "content": "import SlDivider from './divider.component.js';\n\nexport * from './divider.component.js';\nexport default SlDivider;\n\nSlDivider.define('sl-divider');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-divider': SlDivider;\n  }\n}\n"
  },
  {
    "path": "src/components/drawer/drawer.component.ts",
    "content": "import { animateTo, stopAnimations } from '../../internal/animate.js';\nimport { blurActiveElement } from '../../internal/closeActiveElement.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';\nimport { property, query } from 'lit/decorators.js';\nimport { uppercaseFirstLetter } from '../../internal/string.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport Modal from '../../internal/modal.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport styles from './drawer.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Drawers slide in from a container to expose additional options and information.\n * @documentation https://shoelace.style/components/drawer\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - The drawer's main content.\n * @slot label - The drawer's label. Alternatively, you can use the `label` attribute.\n * @slot header-actions - Optional actions to add to the header. Works best with `<sl-icon-button>`.\n * @slot footer - The drawer's footer, usually one or more buttons representing various options.\n *\n * @event sl-show - Emitted when the drawer opens.\n * @event sl-after-show - Emitted after the drawer opens and all animations are complete.\n * @event sl-hide - Emitted when the drawer closes.\n * @event sl-after-hide - Emitted after the drawer closes and all animations are complete.\n * @event sl-initial-focus - Emitted when the drawer opens and is ready to receive focus. Calling\n *   `event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input.\n * @event {{ source: 'close-button' | 'keyboard' | 'overlay' }} sl-request-close - Emitted when the user attempts to\n *   close the drawer by clicking the close button, clicking the overlay, or pressing escape. Calling\n *   `event.preventDefault()` will keep the drawer open. Avoid using this unless closing the drawer will result in\n *   destructive behavior such as data loss.\n *\n * @csspart base - The component's base wrapper.\n * @csspart overlay - The overlay that covers the screen behind the drawer.\n * @csspart panel - The drawer's panel (where the drawer and its content are rendered).\n * @csspart header - The drawer's header. This element wraps the title and header actions.\n * @csspart header-actions - Optional actions to add to the header. Works best with `<sl-icon-button>`.\n * @csspart title - The drawer's title.\n * @csspart close-button - The close button, an `<sl-icon-button>`.\n * @csspart close-button__base - The close button's exported `base` part.\n * @csspart body - The drawer's body.\n * @csspart footer - The drawer's footer.\n *\n * @cssproperty --size - The preferred size of the drawer. This will be applied to the drawer's width or height\n *   depending on its `placement`. Note that the drawer will shrink to accommodate smaller screens.\n * @cssproperty --header-spacing - The amount of padding to use for the header.\n * @cssproperty --body-spacing - The amount of padding to use for the body.\n * @cssproperty --footer-spacing - The amount of padding to use for the footer.\n *\n * @animation drawer.showTop - The animation to use when showing a drawer with `top` placement.\n * @animation drawer.showEnd - The animation to use when showing a drawer with `end` placement.\n * @animation drawer.showBottom - The animation to use when showing a drawer with `bottom` placement.\n * @animation drawer.showStart - The animation to use when showing a drawer with `start` placement.\n * @animation drawer.hideTop - The animation to use when hiding a drawer with `top` placement.\n * @animation drawer.hideEnd - The animation to use when hiding a drawer with `end` placement.\n * @animation drawer.hideBottom - The animation to use when hiding a drawer with `bottom` placement.\n * @animation drawer.hideStart - The animation to use when hiding a drawer with `start` placement.\n * @animation drawer.denyClose - The animation to use when a request to close the drawer is denied.\n * @animation drawer.overlay.show - The animation to use when showing the drawer's overlay.\n * @animation drawer.overlay.hide - The animation to use when hiding the drawer's overlay.\n *\n * @property modal - Exposes the internal modal utility that controls focus trapping. To temporarily disable focus\n *   trapping and allow third-party modals spawned from an active Shoelace modal, call `modal.activateExternal()` when\n *   the third-party modal opens. Upon closing, call `modal.deactivateExternal()` to restore Shoelace's focus trapping.\n */\nexport default class SlDrawer extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon-button': SlIconButton };\n\n  private readonly hasSlotController = new HasSlotController(this, 'footer');\n  private readonly localize = new LocalizeController(this);\n  private originalTrigger: HTMLElement | null;\n  public modal = new Modal(this);\n  private closeWatcher: CloseWatcher | null;\n\n  @query('.drawer') drawer: HTMLElement;\n  @query('.drawer__panel') panel: HTMLElement;\n  @query('.drawer__overlay') overlay: HTMLElement;\n\n  /**\n   * Indicates whether or not the drawer is open. You can toggle this attribute to show and hide the drawer, or you can\n   * use the `show()` and `hide()` methods and this attribute will reflect the drawer's open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /**\n   * The drawer's label as displayed in the header. You should always include a relevant label even when using\n   * `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.\n   */\n  @property({ reflect: true }) label = '';\n\n  /** The direction from which the drawer will open. */\n  @property({ reflect: true }) placement: 'top' | 'end' | 'bottom' | 'start' = 'end';\n\n  /**\n   * By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of\n   * its parent element, set this attribute and add `position: relative` to the parent.\n   */\n  @property({ type: Boolean, reflect: true }) contained = false;\n\n  /**\n   * Removes the header. This will also remove the default close button, so please ensure you provide an easy,\n   * accessible way for users to dismiss the drawer.\n   */\n  @property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;\n\n  firstUpdated() {\n    this.drawer.hidden = !this.open;\n\n    if (this.open) {\n      this.addOpenListeners();\n\n      if (!this.contained) {\n        this.modal.activate();\n        lockBodyScrolling(this);\n      }\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    unlockBodyScrolling(this);\n    this.removeOpenListeners();\n  }\n\n  private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {\n    const slRequestClose = this.emit('sl-request-close', {\n      cancelable: true,\n      detail: { source }\n    });\n\n    if (slRequestClose.defaultPrevented) {\n      const animation = getAnimation(this, 'drawer.denyClose', { dir: this.localize.dir() });\n      animateTo(this.panel, animation.keyframes, animation.options);\n      return;\n    }\n\n    this.hide();\n  }\n\n  private addOpenListeners() {\n    if ('CloseWatcher' in window) {\n      this.closeWatcher?.destroy();\n      if (!this.contained) {\n        this.closeWatcher = new CloseWatcher();\n        this.closeWatcher.onclose = () => this.requestClose('keyboard');\n      }\n    } else {\n      document.addEventListener('keydown', this.handleDocumentKeyDown);\n    }\n  }\n\n  private removeOpenListeners() {\n    document.removeEventListener('keydown', this.handleDocumentKeyDown);\n    this.closeWatcher?.destroy();\n  }\n\n  private handleDocumentKeyDown = (event: KeyboardEvent) => {\n    // Contained drawers aren't modal and don't response to the escape key\n    if (this.contained) {\n      return;\n    }\n\n    if (event.key === 'Escape' && this.modal.isActive() && this.open) {\n      event.stopImmediatePropagation();\n      this.requestClose('keyboard');\n    }\n  };\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open) {\n      // Show\n      this.emit('sl-show');\n      this.addOpenListeners();\n      this.originalTrigger = document.activeElement as HTMLElement;\n\n      // Lock body scrolling only if the drawer isn't contained\n      if (!this.contained) {\n        this.modal.activate();\n        lockBodyScrolling(this);\n      }\n\n      // When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the\n      // drawer's animation to jitter, so we'll temporarily remove the attribute, call `focus({ preventScroll: true })`\n      // ourselves, and add the attribute back afterwards.\n      //\n      // Related: https://github.com/shoelace-style/shoelace/issues/693\n      //\n      const autoFocusTarget = this.querySelector('[autofocus]');\n      if (autoFocusTarget) {\n        autoFocusTarget.removeAttribute('autofocus');\n      }\n\n      await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);\n      this.drawer.hidden = false;\n\n      // Set initial focus\n      requestAnimationFrame(() => {\n        const slInitialFocus = this.emit('sl-initial-focus', { cancelable: true });\n\n        if (!slInitialFocus.defaultPrevented) {\n          // Set focus to the autofocus target and restore the attribute\n          if (autoFocusTarget) {\n            (autoFocusTarget as HTMLInputElement).focus({ preventScroll: true });\n          } else {\n            this.panel.focus({ preventScroll: true });\n          }\n        }\n\n        // Restore the autofocus attribute\n        if (autoFocusTarget) {\n          autoFocusTarget.setAttribute('autofocus', '');\n        }\n      });\n\n      const panelAnimation = getAnimation(this, `drawer.show${uppercaseFirstLetter(this.placement)}`, {\n        dir: this.localize.dir()\n      });\n      const overlayAnimation = getAnimation(this, 'drawer.overlay.show', { dir: this.localize.dir() });\n      await Promise.all([\n        animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),\n        animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)\n      ]);\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      blurActiveElement(this);\n      this.emit('sl-hide');\n      this.removeOpenListeners();\n\n      if (!this.contained) {\n        this.modal.deactivate();\n        unlockBodyScrolling(this);\n      }\n\n      await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);\n      const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, {\n        dir: this.localize.dir()\n      });\n      const overlayAnimation = getAnimation(this, 'drawer.overlay.hide', { dir: this.localize.dir() });\n\n      // Animate the overlay and the panel at the same time. Because animation durations might be different, we need to\n      // hide each one individually when the animation finishes, otherwise the first one that finishes will reappear\n      // unexpectedly. We'll unhide them after all animations have completed.\n      await Promise.all([\n        animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options).then(() => {\n          this.overlay.hidden = true;\n        }),\n        animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options).then(() => {\n          this.panel.hidden = true;\n        })\n      ]);\n\n      this.drawer.hidden = true;\n\n      // Now that the dialog is hidden, restore the overlay and panel for next time\n      this.overlay.hidden = false;\n      this.panel.hidden = false;\n\n      // Restore focus to the original trigger\n      const trigger = this.originalTrigger;\n      if (typeof trigger?.focus === 'function') {\n        setTimeout(() => trigger.focus());\n      }\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  @watch('contained', { waitUntilFirstUpdate: true })\n  handleNoModalChange() {\n    if (this.open && !this.contained) {\n      this.modal.activate();\n      lockBodyScrolling(this);\n    }\n\n    if (this.open && this.contained) {\n      this.modal.deactivate();\n      unlockBodyScrolling(this);\n    }\n  }\n\n  /** Shows the drawer. */\n  async show() {\n    if (this.open) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the drawer */\n  async hide() {\n    if (!this.open) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          drawer: true,\n          'drawer--open': this.open,\n          'drawer--top': this.placement === 'top',\n          'drawer--end': this.placement === 'end',\n          'drawer--bottom': this.placement === 'bottom',\n          'drawer--start': this.placement === 'start',\n          'drawer--contained': this.contained,\n          'drawer--fixed': !this.contained,\n          'drawer--rtl': this.localize.dir() === 'rtl',\n          'drawer--has-footer': this.hasSlotController.test('footer')\n        })}\n      >\n        <div part=\"overlay\" class=\"drawer__overlay\" @click=${() => this.requestClose('overlay')} tabindex=\"-1\"></div>\n\n        <div\n          part=\"panel\"\n          class=\"drawer__panel\"\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-hidden=${this.open ? 'false' : 'true'}\n          aria-label=${ifDefined(this.noHeader ? this.label : undefined)}\n          aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}\n          tabindex=\"0\"\n        >\n          ${!this.noHeader\n            ? html`\n                <header part=\"header\" class=\"drawer__header\">\n                  <h2 part=\"title\" class=\"drawer__title\" id=\"title\">\n                    <!-- If there's no label, use an invisible character to prevent the header from collapsing -->\n                    <slot name=\"label\"> ${this.label.length > 0 ? this.label : String.fromCharCode(65279)} </slot>\n                  </h2>\n                  <div part=\"header-actions\" class=\"drawer__header-actions\">\n                    <slot name=\"header-actions\"></slot>\n                    <sl-icon-button\n                      part=\"close-button\"\n                      exportparts=\"base:close-button__base\"\n                      class=\"drawer__close\"\n                      name=\"x-lg\"\n                      label=${this.localize.term('close')}\n                      library=\"system\"\n                      @click=${() => this.requestClose('close-button')}\n                    ></sl-icon-button>\n                  </div>\n                </header>\n              `\n            : ''}\n\n          <slot part=\"body\" class=\"drawer__body\"></slot>\n\n          <footer part=\"footer\" class=\"drawer__footer\">\n            <slot name=\"footer\"></slot>\n          </footer>\n        </div>\n      </div>\n    `;\n  }\n}\n\n// Top\nsetDefaultAnimation('drawer.showTop', {\n  keyframes: [\n    { opacity: 0, translate: '0 -100%' },\n    { opacity: 1, translate: '0 0' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('drawer.hideTop', {\n  keyframes: [\n    { opacity: 1, translate: '0 0' },\n    { opacity: 0, translate: '0 -100%' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\n// End\nsetDefaultAnimation('drawer.showEnd', {\n  keyframes: [\n    { opacity: 0, translate: '100%' },\n    { opacity: 1, translate: '0' }\n  ],\n  rtlKeyframes: [\n    { opacity: 0, translate: '-100%' },\n    { opacity: 1, translate: '0' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('drawer.hideEnd', {\n  keyframes: [\n    { opacity: 1, translate: '0' },\n    { opacity: 0, translate: '100%' }\n  ],\n  rtlKeyframes: [\n    { opacity: 1, translate: '0' },\n    { opacity: 0, translate: '-100%' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\n// Bottom\nsetDefaultAnimation('drawer.showBottom', {\n  keyframes: [\n    { opacity: 0, translate: '0 100%' },\n    { opacity: 1, translate: '0 0' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('drawer.hideBottom', {\n  keyframes: [\n    { opacity: 1, translate: '0 0' },\n    { opacity: 0, translate: '0 100%' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\n// Start\nsetDefaultAnimation('drawer.showStart', {\n  keyframes: [\n    { opacity: 0, translate: '-100%' },\n    { opacity: 1, translate: '0' }\n  ],\n  rtlKeyframes: [\n    { opacity: 0, translate: '100%' },\n    { opacity: 1, translate: '0' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\nsetDefaultAnimation('drawer.hideStart', {\n  keyframes: [\n    { opacity: 1, translate: '0' },\n    { opacity: 0, translate: '-100%' }\n  ],\n  rtlKeyframes: [\n    { opacity: 1, translate: '0' },\n    { opacity: 0, translate: '100%' }\n  ],\n  options: { duration: 250, easing: 'ease' }\n});\n\n// Deny close\nsetDefaultAnimation('drawer.denyClose', {\n  keyframes: [{ scale: 1 }, { scale: 1.01 }, { scale: 1 }],\n  options: { duration: 250 }\n});\n\n// Overlay\nsetDefaultAnimation('drawer.overlay.show', {\n  keyframes: [{ opacity: 0 }, { opacity: 1 }],\n  options: { duration: 250 }\n});\n\nsetDefaultAnimation('drawer.overlay.hide', {\n  keyframes: [{ opacity: 1 }, { opacity: 0 }],\n  options: { duration: 250 }\n});\n"
  },
  {
    "path": "src/components/drawer/drawer.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --size: 25rem;\n    --header-spacing: var(--sl-spacing-large);\n    --body-spacing: var(--sl-spacing-large);\n    --footer-spacing: var(--sl-spacing-large);\n\n    display: contents;\n  }\n\n  .drawer {\n    top: 0;\n    inset-inline-start: 0;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n    overflow: hidden;\n  }\n\n  .drawer--contained {\n    position: absolute;\n    z-index: initial;\n  }\n\n  .drawer--fixed {\n    position: fixed;\n    z-index: var(--sl-z-index-drawer);\n  }\n\n  .drawer__panel {\n    position: absolute;\n    display: flex;\n    flex-direction: column;\n    z-index: 2;\n    max-width: 100%;\n    max-height: 100%;\n    background-color: var(--sl-panel-background-color);\n    box-shadow: var(--sl-shadow-x-large);\n    overflow: auto;\n    pointer-events: all;\n  }\n\n  .drawer__panel:focus {\n    outline: none;\n  }\n\n  .drawer--top .drawer__panel {\n    top: 0;\n    inset-inline-end: auto;\n    bottom: auto;\n    inset-inline-start: 0;\n    width: 100%;\n    height: var(--size);\n  }\n\n  .drawer--end .drawer__panel {\n    top: 0;\n    inset-inline-end: 0;\n    bottom: auto;\n    inset-inline-start: auto;\n    width: var(--size);\n    height: 100%;\n  }\n\n  .drawer--bottom .drawer__panel {\n    top: auto;\n    inset-inline-end: auto;\n    bottom: 0;\n    inset-inline-start: 0;\n    width: 100%;\n    height: var(--size);\n  }\n\n  .drawer--start .drawer__panel {\n    top: 0;\n    inset-inline-end: auto;\n    bottom: auto;\n    inset-inline-start: 0;\n    width: var(--size);\n    height: 100%;\n  }\n\n  .drawer__header {\n    display: flex;\n  }\n\n  .drawer__title {\n    flex: 1 1 auto;\n    font: inherit;\n    font-size: var(--sl-font-size-large);\n    line-height: var(--sl-line-height-dense);\n    padding: var(--header-spacing);\n    margin: 0;\n  }\n\n  .drawer__header-actions {\n    flex-shrink: 0;\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: end;\n    gap: var(--sl-spacing-2x-small);\n    padding: 0 var(--header-spacing);\n  }\n\n  .drawer__header-actions sl-icon-button,\n  .drawer__header-actions ::slotted(sl-icon-button) {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    font-size: var(--sl-font-size-medium);\n  }\n\n  .drawer__body {\n    flex: 1 1 auto;\n    display: block;\n    padding: var(--body-spacing);\n    overflow: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n\n  .drawer__footer {\n    text-align: right;\n    padding: var(--footer-spacing);\n  }\n\n  .drawer__footer ::slotted(sl-button:not(:last-of-type)) {\n    margin-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .drawer:not(.drawer--has-footer) .drawer__footer {\n    display: none;\n  }\n\n  .drawer__overlay {\n    display: block;\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    background-color: var(--sl-overlay-background-color);\n    pointer-events: all;\n  }\n\n  .drawer--contained .drawer__overlay {\n    display: none;\n  }\n\n  @media (forced-colors: active) {\n    .drawer__panel {\n      border: solid 1px var(--sl-color-neutral-0);\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/drawer/drawer.test.ts",
    "content": "import '../../../dist/shoelace.js';\n// cspell:dictionaries lorem-ipsum\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlDrawer from './drawer.js';\n\ndescribe('<sl-drawer>', () => {\n  it('should be visible with the open attribute', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should not be visible without the open attribute', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when calling show()', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.show();\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.hide();\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when setting open = true', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(base.hidden).to.be.true;\n  });\n\n  it('should not close when sl-request-close is prevented', async () => {\n    const el = await fixture<SlDrawer>(html`\n      <sl-drawer open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</sl-drawer>\n    `);\n    const overlay = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"overlay\"]')!;\n\n    el.addEventListener('sl-request-close', event => {\n      event.preventDefault();\n    });\n    overlay.click();\n\n    expect(el.open).to.be.true;\n  });\n\n  it('should allow initial focus to be set', async () => {\n    const el = await fixture<SlDrawer>(html` <sl-drawer><input /></sl-drawer> `);\n    const input = el.querySelector<HTMLInputElement>('input')!;\n    const initialFocusHandler = sinon.spy((event: InputEvent) => {\n      event.preventDefault();\n      input.focus();\n    });\n\n    el.addEventListener('sl-initial-focus', initialFocusHandler);\n    el.show();\n\n    await waitUntil(() => initialFocusHandler.calledOnce);\n\n    expect(initialFocusHandler).to.have.been.calledOnce;\n    expect(document.activeElement).to.equal(input);\n  });\n\n  it('should close when pressing Escape', async () => {\n    const el = await fixture<SlDrawer>(html` <sl-drawer open></sl-drawer> `);\n    const hideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n\n    await sendKeys({ press: 'Escape' });\n    await waitUntil(() => hideHandler.calledOnce);\n\n    expect(el.open).to.be.false;\n  });\n});\n"
  },
  {
    "path": "src/components/drawer/drawer.ts",
    "content": "import SlDrawer from './drawer.component.js';\n\nexport * from './drawer.component.js';\nexport default SlDrawer;\n\nSlDrawer.define('sl-drawer');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-drawer': SlDrawer;\n  }\n}\n"
  },
  {
    "path": "src/components/dropdown/dropdown.component.ts",
    "content": "import { animateTo, stopAnimations } from '../../internal/animate.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { getDeepestActiveElement } from '../../internal/active-elements.js';\nimport { getTabbableBoundary } from '../../internal/tabbable.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlPopup from '../popup/popup.component.js';\nimport styles from './dropdown.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { SlSelectEvent } from '../../events/sl-select.js';\nimport type SlButton from '../button/button.js';\nimport type SlIconButton from '../icon-button/icon-button.js';\nimport type SlMenu from '../menu/menu.js';\n\n/**\n * @summary Dropdowns expose additional content that \"drops down\" in a panel.\n * @documentation https://shoelace.style/components/dropdown\n * @status stable\n * @since 2.0\n *\n * @dependency sl-popup\n *\n * @slot - The dropdown's main content.\n * @slot trigger - The dropdown's trigger, usually a `<sl-button>` element.\n *\n * @event sl-show - Emitted when the dropdown opens.\n * @event sl-after-show - Emitted after the dropdown opens and all animations are complete.\n * @event sl-hide - Emitted when the dropdown closes.\n * @event sl-after-hide - Emitted after the dropdown closes and all animations are complete.\n *\n * @csspart base - The component's base wrapper, an `<sl-popup>` element.\n * @csspart base__popup - The popup's exported `popup` part. Use this to target the tooltip's popup container.\n * @csspart trigger - The container that wraps the trigger.\n * @csspart panel - The panel that gets shown when the dropdown is open.\n *\n * @animation dropdown.show - The animation to use when showing the dropdown.\n * @animation dropdown.hide - The animation to use when hiding the dropdown.\n */\nexport default class SlDropdown extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-popup': SlPopup };\n\n  @query('.dropdown') popup: SlPopup;\n  @query('.dropdown__trigger') trigger: HTMLSlotElement;\n  @query('.dropdown__panel') panel: HTMLSlotElement;\n\n  private readonly localize = new LocalizeController(this);\n  private closeWatcher: CloseWatcher | null;\n\n  /**\n   * Indicates whether or not the dropdown is open. You can toggle this attribute to show and hide the dropdown, or you\n   * can use the `show()` and `hide()` methods and this attribute will reflect the dropdown's open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /**\n   * The preferred placement of the dropdown panel. Note that the actual placement may vary as needed to keep the panel\n   * inside of the viewport.\n   */\n  @property({ reflect: true }) placement:\n    | 'top'\n    | 'top-start'\n    | 'top-end'\n    | 'bottom'\n    | 'bottom-start'\n    | 'bottom-end'\n    | 'right'\n    | 'right-start'\n    | 'right-end'\n    | 'left'\n    | 'left-start'\n    | 'left-end' = 'bottom-start';\n\n  /** Disables the dropdown so the panel will not open. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * By default, the dropdown is closed when an item is selected. This attribute will keep it open instead. Useful for\n   * dropdowns that allow for multiple interactions.\n   */\n  @property({ attribute: 'stay-open-on-select', type: Boolean, reflect: true }) stayOpenOnSelect = false;\n\n  /**\n   * The dropdown will close when the user interacts outside of this element (e.g. clicking). Useful for composing other\n   * components that use a dropdown internally.\n   */\n  @property({ attribute: false }) containingElement?: HTMLElement;\n\n  /** The distance in pixels from which to offset the panel away from its trigger. */\n  @property({ type: Number }) distance = 0;\n\n  /** The distance in pixels from which to offset the panel along its trigger. */\n  @property({ type: Number }) skidding = 0;\n\n  /**\n   * Enable this option to prevent the panel from being clipped when the component is placed inside a container with\n   * `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.\n   */\n  @property({ type: Boolean }) hoist = false;\n\n  /**\n   * Syncs the popup width or height to that of the trigger element.\n   */\n  @property({ reflect: true }) sync: 'width' | 'height' | 'both' | undefined = undefined;\n\n  connectedCallback() {\n    super.connectedCallback();\n\n    if (!this.containingElement) {\n      this.containingElement = this;\n    }\n  }\n\n  firstUpdated() {\n    this.panel.hidden = !this.open;\n\n    // If the dropdown is visible on init, update its position\n    if (this.open) {\n      this.addOpenListeners();\n      this.popup.active = true;\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.removeOpenListeners();\n    this.hide();\n  }\n\n  focusOnTrigger() {\n    const trigger = this.trigger.assignedElements({ flatten: true })[0] as HTMLElement | undefined;\n    if (typeof trigger?.focus === 'function') {\n      trigger.focus();\n    }\n  }\n\n  getMenu() {\n    return this.panel.assignedElements({ flatten: true }).find(el => el.tagName.toLowerCase() === 'sl-menu') as\n      | SlMenu\n      | undefined;\n  }\n\n  private handleKeyDown = (event: KeyboardEvent) => {\n    // Close when escape is pressed inside an open dropdown. We need to listen on the panel itself and stop propagation\n    // in case any ancestors are also listening for this key.\n    if (this.open && event.key === 'Escape') {\n      event.stopPropagation();\n      this.hide();\n      this.focusOnTrigger();\n    }\n  };\n\n  private handleDocumentKeyDown = (event: KeyboardEvent) => {\n    // Close when escape or tab is pressed\n    if (event.key === 'Escape' && this.open && !this.closeWatcher) {\n      event.stopPropagation();\n      this.focusOnTrigger();\n      this.hide();\n      return;\n    }\n\n    // Handle tabbing\n    if (event.key === 'Tab') {\n      // Tabbing within an open menu should close the dropdown and refocus the trigger\n      if (this.open && document.activeElement?.tagName.toLowerCase() === 'sl-menu-item') {\n        event.preventDefault();\n        this.hide();\n        this.focusOnTrigger();\n        return;\n      }\n\n      const computeClosestContaining = (element: Element | null | undefined, tagName: string): Element | null => {\n        if (!element) return null;\n\n        const closest = element.closest(tagName);\n        if (closest) return closest;\n\n        const rootNode = element.getRootNode();\n        if (rootNode instanceof ShadowRoot) {\n          return computeClosestContaining(rootNode.host, tagName);\n        }\n\n        return null;\n      };\n\n      // Tabbing outside of the containing element closes the panel\n      //\n      // If the dropdown is used within a shadow DOM, we need to obtain the activeElement within that shadowRoot,\n      // otherwise `document.activeElement` will only return the name of the parent shadow DOM element.\n      setTimeout(() => {\n        const activeElement =\n          this.containingElement?.getRootNode() instanceof ShadowRoot\n            ? getDeepestActiveElement()\n            : document.activeElement;\n\n        if (\n          !this.containingElement ||\n          computeClosestContaining(activeElement, this.containingElement.tagName.toLowerCase()) !==\n            this.containingElement\n        ) {\n          this.hide();\n        }\n      });\n    }\n  };\n\n  private handleDocumentMouseDown = (event: MouseEvent) => {\n    // Close when clicking outside of the containing element\n    const path = event.composedPath();\n    if (this.containingElement && !path.includes(this.containingElement)) {\n      this.hide();\n    }\n  };\n\n  private handlePanelSelect = (event: SlSelectEvent) => {\n    const target = event.target as HTMLElement;\n\n    // Hide the dropdown when a menu item is selected\n    if (!this.stayOpenOnSelect && target.tagName.toLowerCase() === 'sl-menu') {\n      this.hide();\n      this.focusOnTrigger();\n    }\n  };\n\n  handleTriggerClick() {\n    if (this.open) {\n      this.hide();\n    } else {\n      this.show();\n      this.focusOnTrigger();\n    }\n  }\n\n  async handleTriggerKeyDown(event: KeyboardEvent) {\n    // When spacebar/enter is pressed, show the panel but don't focus on the menu. This let's the user press the same\n    // key again to hide the menu in case they don't want to make a selection.\n    if ([' ', 'Enter'].includes(event.key)) {\n      event.preventDefault();\n      this.handleTriggerClick();\n      return;\n    }\n\n    const menu = this.getMenu();\n\n    if (menu) {\n      const menuItems = menu.getAllItems();\n      const firstMenuItem = menuItems[0];\n      const lastMenuItem = menuItems[menuItems.length - 1];\n\n      // When up/down is pressed, we make the assumption that the user is familiar with the menu and plans to make a\n      // selection. Rather than toggle the panel, we focus on the menu (if one exists) and activate the first item for\n      // faster navigation.\n      if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {\n        event.preventDefault();\n\n        // Show the menu if it's not already open\n        if (!this.open) {\n          this.show();\n\n          // Wait for the dropdown to open before focusing, but not the animation\n          await this.updateComplete;\n        }\n\n        if (menuItems.length > 0) {\n          // Focus on the first/last menu item after showing\n          this.updateComplete.then(() => {\n            if (event.key === 'ArrowDown' || event.key === 'Home') {\n              menu.setCurrentItem(firstMenuItem);\n              firstMenuItem.focus();\n            }\n\n            if (event.key === 'ArrowUp' || event.key === 'End') {\n              menu.setCurrentItem(lastMenuItem);\n              lastMenuItem.focus();\n            }\n          });\n        }\n      }\n    }\n  }\n\n  handleTriggerKeyUp(event: KeyboardEvent) {\n    // Prevent space from triggering a click event in Firefox\n    if (event.key === ' ') {\n      event.preventDefault();\n    }\n  }\n\n  handleTriggerSlotChange() {\n    this.updateAccessibleTrigger();\n  }\n\n  //\n  // Slotted triggers can be arbitrary content, but we need to link them to the dropdown panel with `aria-haspopup` and\n  // `aria-expanded`. These must be applied to the \"accessible trigger\" (the tabbable portion of the trigger element\n  // that gets slotted in) so screen readers will understand them. The accessible trigger could be the slotted element,\n  // a child of the slotted element, or an element in the slotted element's shadow root.\n  //\n  // For example, the accessible trigger of an <sl-button> is a <button> located inside its shadow root.\n  //\n  // To determine this, we assume the first tabbable element in the trigger slot is the \"accessible trigger.\"\n  //\n  updateAccessibleTrigger() {\n    const assignedElements = this.trigger.assignedElements({ flatten: true }) as HTMLElement[];\n    const accessibleTrigger = assignedElements.find(el => getTabbableBoundary(el).start);\n    let target: HTMLElement;\n\n    if (accessibleTrigger) {\n      switch (accessibleTrigger.tagName.toLowerCase()) {\n        // Shoelace buttons have to update the internal button so it's announced correctly by screen readers\n        case 'sl-button':\n        case 'sl-icon-button':\n          target = (accessibleTrigger as SlButton | SlIconButton).button;\n          break;\n\n        default:\n          target = accessibleTrigger;\n      }\n\n      target.setAttribute('aria-haspopup', 'true');\n      target.setAttribute('aria-expanded', this.open ? 'true' : 'false');\n    }\n  }\n\n  /** Shows the dropdown panel. */\n  async show() {\n    if (this.open) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the dropdown panel */\n  async hide() {\n    if (!this.open) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  /**\n   * Instructs the dropdown menu to reposition. Useful when the position or size of the trigger changes when the menu\n   * is activated.\n   */\n  reposition() {\n    this.popup.reposition();\n  }\n\n  addOpenListeners() {\n    this.panel.addEventListener('sl-select', this.handlePanelSelect);\n    if ('CloseWatcher' in window) {\n      this.closeWatcher?.destroy();\n      this.closeWatcher = new CloseWatcher();\n      this.closeWatcher.onclose = () => {\n        this.hide();\n        this.focusOnTrigger();\n      };\n    } else {\n      this.panel.addEventListener('keydown', this.handleKeyDown);\n    }\n    document.addEventListener('keydown', this.handleDocumentKeyDown);\n    document.addEventListener('mousedown', this.handleDocumentMouseDown);\n  }\n\n  removeOpenListeners() {\n    if (this.panel) {\n      this.panel.removeEventListener('sl-select', this.handlePanelSelect);\n      this.panel.removeEventListener('keydown', this.handleKeyDown);\n    }\n    document.removeEventListener('keydown', this.handleDocumentKeyDown);\n    document.removeEventListener('mousedown', this.handleDocumentMouseDown);\n    this.closeWatcher?.destroy();\n  }\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.disabled) {\n      this.open = false;\n      return;\n    }\n\n    this.updateAccessibleTrigger();\n\n    if (this.open) {\n      // Show\n      this.emit('sl-show');\n      this.addOpenListeners();\n\n      await stopAnimations(this);\n      this.panel.hidden = false;\n      this.popup.active = true;\n      const { keyframes, options } = getAnimation(this, 'dropdown.show', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      this.emit('sl-hide');\n      this.removeOpenListeners();\n\n      await stopAnimations(this);\n      const { keyframes, options } = getAnimation(this, 'dropdown.hide', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n      this.panel.hidden = true;\n      this.popup.active = false;\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  render() {\n    return html`\n      <sl-popup\n        part=\"base\"\n        exportparts=\"popup:base__popup\"\n        id=\"dropdown\"\n        placement=${this.placement}\n        distance=${this.distance}\n        skidding=${this.skidding}\n        strategy=${this.hoist ? 'fixed' : 'absolute'}\n        flip\n        shift\n        auto-size=\"vertical\"\n        auto-size-padding=\"10\"\n        sync=${ifDefined(this.sync ? this.sync : undefined)}\n        class=${classMap({\n          dropdown: true,\n          'dropdown--open': this.open\n        })}\n      >\n        <slot\n          name=\"trigger\"\n          slot=\"anchor\"\n          part=\"trigger\"\n          class=\"dropdown__trigger\"\n          @click=${this.handleTriggerClick}\n          @keydown=${this.handleTriggerKeyDown}\n          @keyup=${this.handleTriggerKeyUp}\n          @slotchange=${this.handleTriggerSlotChange}\n        ></slot>\n\n        <div aria-hidden=${this.open ? 'false' : 'true'} aria-labelledby=\"dropdown\">\n          <slot part=\"panel\" class=\"dropdown__panel\"></slot>\n        </div>\n      </sl-popup>\n    `;\n  }\n}\n\nsetDefaultAnimation('dropdown.show', {\n  keyframes: [\n    { opacity: 0, scale: 0.9 },\n    { opacity: 1, scale: 1 }\n  ],\n  options: { duration: 100, easing: 'ease' }\n});\n\nsetDefaultAnimation('dropdown.hide', {\n  keyframes: [\n    { opacity: 1, scale: 1 },\n    { opacity: 0, scale: 0.9 }\n  ],\n  options: { duration: 100, easing: 'ease' }\n});\n"
  },
  {
    "path": "src/components/dropdown/dropdown.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  .dropdown::part(popup) {\n    z-index: var(--sl-z-index-dropdown);\n  }\n\n  .dropdown[data-current-placement^='top']::part(popup) {\n    transform-origin: bottom;\n  }\n\n  .dropdown[data-current-placement^='bottom']::part(popup) {\n    transform-origin: top;\n  }\n\n  .dropdown[data-current-placement^='left']::part(popup) {\n    transform-origin: right;\n  }\n\n  .dropdown[data-current-placement^='right']::part(popup) {\n    transform-origin: left;\n  }\n\n  .dropdown__trigger {\n    display: block;\n  }\n\n  .dropdown__panel {\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    box-shadow: var(--sl-shadow-large);\n    border-radius: var(--sl-border-radius-medium);\n    pointer-events: none;\n  }\n\n  .dropdown--open .dropdown__panel {\n    display: block;\n    pointer-events: all;\n  }\n\n  /* When users slot a menu, make sure it conforms to the popup's auto-size */\n  ::slotted(sl-menu) {\n    max-width: var(--auto-size-available-width) !important;\n    max-height: var(--auto-size-available-height) !important;\n  }\n`;\n"
  },
  {
    "path": "src/components/dropdown/dropdown.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { clickOnElement } from '../../internal/test.js';\nimport { customElement } from 'lit/decorators.js';\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport { LitElement } from 'lit';\nimport { sendKeys, sendMouse } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlDropdown from './dropdown.js';\n\ndescribe('<sl-dropdown>', () => {\n  it('should be visible with the open attribute', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown open>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n\n    expect(panel.hidden).to.be.false;\n  });\n\n  it('should not be visible without the open attribute', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n\n    expect(panel.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when calling show()', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.show();\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(panel.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown open>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.hide();\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(panel.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when setting open = true', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(panel.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown open>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"panel\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(panel.hidden).to.be.true;\n  });\n\n  it('should still open on arrow navigation when no menu items', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu> </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.focus();\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.true;\n  });\n\n  it('should open on arrow down navigation', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n    const firstMenuItem = el.querySelectorAll('sl-menu-item')[0];\n\n    trigger.focus();\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.true;\n    expect(document.activeElement).to.equal(firstMenuItem);\n  });\n\n  it('should open on arrow up navigation', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n    const secondMenuItem = el.querySelectorAll('sl-menu-item')[1];\n\n    trigger.focus();\n    await sendKeys({ press: 'ArrowUp' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.true;\n    expect(document.activeElement).to.equal(secondMenuItem);\n  });\n\n  it('should navigate to first focusable item on arrow navigation', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-label>Top Label</sl-menu-label>\n          <sl-menu-item>Item 1</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n    const item = el.querySelector('sl-menu-item')!;\n\n    await clickOnElement(trigger);\n    await trigger.updateComplete;\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n\n    expect(document.activeElement).to.equal(item);\n  });\n\n  it('should close on escape key', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown open>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.focus();\n    await sendKeys({ press: 'Escape' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.false;\n  });\n\n  it('should not open on arrow navigation when no menu exists', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <div>Some custom content</div>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.focus();\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.false;\n  });\n\n  it('should open on enter key', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.focus();\n    await el.updateComplete;\n    await sendKeys({ press: 'Enter' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.true;\n  });\n\n  it('should focus on menu items when clicking the trigger and arrowing through options', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n          <sl-menu-item>Item 2</sl-menu-item>\n          <sl-menu-item>Item 3</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n    const secondMenuItem = el.querySelectorAll('sl-menu-item')[1];\n\n    await clickOnElement(trigger);\n    await trigger.updateComplete;\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n    await sendKeys({ press: 'ArrowDown' });\n    await el.updateComplete;\n\n    expect(document.activeElement).to.equal(secondMenuItem);\n  });\n\n  it('should open on enter key when no menu exists', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <div>Some custom content</div>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.focus();\n    await el.updateComplete;\n    await sendKeys({ press: 'Enter' });\n    await el.updateComplete;\n\n    expect(el.open).to.be.true;\n  });\n\n  it('should hide when clicked outside container and initially open', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown open>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n\n    await sendMouse({ type: 'click', position: [0, 0] });\n    await el.updateComplete;\n\n    expect(el.open).to.be.false;\n  });\n\n  it('should hide when clicked outside container', async () => {\n    const el = await fixture<SlDropdown>(html`\n      <sl-dropdown>\n        <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n        <sl-menu>\n          <sl-menu-item>Item 1</sl-menu-item>\n        </sl-menu>\n      </sl-dropdown>\n    `);\n    const trigger = el.querySelector('sl-button')!;\n\n    trigger.click();\n    await el.updateComplete;\n    await sendMouse({ type: 'click', position: [0, 0] });\n    await el.updateComplete;\n\n    expect(el.open).to.be.false;\n  });\n\n  describe('when a sl-menu is provided and the dropdown is opened', () => {\n    before(() => {\n      @customElement('custom-wrapper')\n      class Wrapper extends LitElement {\n        render() {\n          return html`<nested-dropdown></nested-dropdown>`;\n        }\n      }\n      // eslint-disable-next-line chai-friendly/no-unused-expressions\n      Wrapper;\n\n      @customElement('nested-dropdown')\n      class NestedDropdown extends LitElement {\n        render() {\n          return html`\n            <sl-dropdown>\n              <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n              <sl-menu>\n                <sl-menu-item>Item 1</sl-menu-item>\n              </sl-menu>\n            </sl-dropdown>\n          `;\n        }\n      }\n      // eslint-disable-next-line chai-friendly/no-unused-expressions\n      NestedDropdown;\n    });\n\n    it('should remain open on tab key', async () => {\n      const el = await fixture<SlDropdown>(html`<custom-wrapper></custom-wrapper>`);\n\n      const dropdown = el.shadowRoot!.querySelector('nested-dropdown')!.shadowRoot!.querySelector('sl-dropdown')!;\n\n      const trigger = dropdown.querySelector('sl-button')!;\n\n      trigger.focus();\n      await dropdown.updateComplete;\n      await sendKeys({ press: 'Enter' });\n      await dropdown.updateComplete;\n      await sendKeys({ press: 'Tab' });\n      await dropdown.updateComplete;\n\n      expect(dropdown.open).to.be.true;\n    });\n  });\n\n  describe('when arbitrary content is provided and the dropdown is opened', () => {\n    before(() => {\n      @customElement('custom-wrapper-arbitrary')\n      class WrapperArbitrary extends LitElement {\n        render() {\n          return html`<nested-dropdown-arbitrary></nested-dropdown-arbitrary>`;\n        }\n      }\n      // eslint-disable-next-line chai-friendly/no-unused-expressions\n      WrapperArbitrary;\n\n      @customElement('nested-dropdown-arbitrary')\n      class NestedDropdownArbitrary extends LitElement {\n        render() {\n          return html`\n            <sl-dropdown>\n              <sl-button slot=\"trigger\" caret>Toggle</sl-button>\n              <ul>\n                <li><a href=\"/settings\">Settings</a></li>\n                <li><a href=\"/profile\">Profile</a></li>\n              </ul>\n            </sl-dropdown>\n          `;\n        }\n      }\n      // eslint-disable-next-line chai-friendly/no-unused-expressions\n      NestedDropdownArbitrary;\n    });\n\n    it('should remain open on tab key', async () => {\n      const el = await fixture<SlDropdown>(html`<custom-wrapper-arbitrary></custom-wrapper-arbitrary>`);\n\n      const dropdown = el\n        .shadowRoot!.querySelector('nested-dropdown-arbitrary')!\n        .shadowRoot!.querySelector('sl-dropdown')!;\n\n      const trigger = dropdown.querySelector('sl-button')!;\n\n      trigger.focus();\n      await dropdown.updateComplete;\n      await sendKeys({ press: 'Enter' });\n      await dropdown.updateComplete;\n      await sendKeys({ press: 'Tab' });\n      await dropdown.updateComplete;\n\n      expect(dropdown.open).to.be.true;\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/dropdown/dropdown.ts",
    "content": "import SlDropdown from './dropdown.component.js';\n\nexport * from './dropdown.component.js';\nexport default SlDropdown;\n\nSlDropdown.define('sl-dropdown');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-dropdown': SlDropdown;\n  }\n}\n"
  },
  {
    "path": "src/components/format-bytes/format-bytes.component.ts",
    "content": "import { LocalizeController } from '../../utilities/localize.js';\nimport { property } from 'lit/decorators.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\n\n/**\n * @summary Formats a number as a human readable bytes value.\n * @documentation https://shoelace.style/components/format-bytes\n * @status stable\n * @since 2.0\n */\nexport default class SlFormatBytes extends ShoelaceElement {\n  private readonly localize = new LocalizeController(this);\n\n  /** The number to format in bytes. */\n  @property({ type: Number }) value = 0;\n\n  /** The type of unit to display. */\n  @property() unit: 'byte' | 'bit' = 'byte';\n\n  /** Determines how to display the result, e.g. \"100 bytes\", \"100 b\", or \"100b\". */\n  @property() display: 'long' | 'short' | 'narrow' = 'short';\n\n  render() {\n    if (isNaN(this.value)) {\n      return '';\n    }\n\n    const bitPrefixes = ['', 'kilo', 'mega', 'giga', 'tera']; // petabit isn't a supported unit\n    const bytePrefixes = ['', 'kilo', 'mega', 'giga', 'tera', 'peta'];\n    const prefix = this.unit === 'bit' ? bitPrefixes : bytePrefixes;\n    const index = Math.max(0, Math.min(Math.floor(Math.log10(this.value) / 3), prefix.length - 1));\n    const unit = prefix[index] + this.unit;\n    const valueToFormat = parseFloat((this.value / Math.pow(1000, index)).toPrecision(3));\n\n    return this.localize.number(valueToFormat, {\n      style: 'unit',\n      unit,\n      unitDisplay: this.display\n    });\n  }\n}\n"
  },
  {
    "path": "src/components/format-bytes/format-bytes.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { elementUpdated, expect, fixture, html } from '@open-wc/testing';\nimport type SlFormatBytes from './format-bytes.js';\n\ndescribe('<sl-format-bytes>', () => {\n  describe('defaults ', () => {\n    it('default properties', async () => {\n      const el = await fixture<SlFormatBytes>(html` <sl-format-bytes></sl-format-bytes> `);\n\n      expect(el.value).to.equal(0);\n      expect(el.unit).to.equal('byte');\n      expect(el.display).to.equal('short');\n      expect(el.lang).to.be.undefined;\n    });\n  });\n\n  describe('bytes', () => {\n    const results = [\n      {\n        value: 12,\n        short: '12 byte',\n        long: '12 bytes',\n        narrow: '12B'\n      },\n      {\n        value: 1200,\n        short: '1.2 kB',\n        long: '1.2 kilobytes',\n        narrow: '1.2kB'\n      },\n      {\n        value: 1200000,\n        short: '1.2 MB',\n        long: '1.2 megabytes',\n        narrow: '1.2MB'\n      },\n      {\n        value: 1200000000,\n        short: '1.2 GB',\n        long: '1.2 gigabytes',\n        narrow: '1.2GB'\n      }\n    ];\n\n    results.forEach(expected => {\n      it('bytes : display formats', async () => {\n        const el = await fixture<SlFormatBytes>(html` <sl-format-bytes></sl-format-bytes> `);\n        // short\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.short);\n\n        // long\n        el.display = 'long';\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.long);\n\n        // narrow\n        el.display = 'narrow';\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.narrow);\n      });\n    });\n  });\n\n  describe('bits', () => {\n    const results = [\n      {\n        value: 12,\n        short: '12 bit',\n        long: '12 bits',\n        narrow: '12bit'\n      },\n      {\n        value: 1200,\n        short: '1.2 kb',\n        long: '1.2 kilobits',\n        narrow: '1.2kb'\n      },\n      {\n        value: 1200000,\n        short: '1.2 Mb',\n        long: '1.2 megabits',\n        narrow: '1.2Mb'\n      },\n      {\n        value: 1200000000,\n        short: '1.2 Gb',\n        long: '1.2 gigabits',\n        narrow: '1.2Gb'\n      }\n    ];\n\n    results.forEach(expected => {\n      it('bits : display formats', async () => {\n        const el = await fixture<SlFormatBytes>(html` <sl-format-bytes unit=\"bit\"></sl-format-bytes> `);\n        // short\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.short);\n\n        // long\n        el.display = 'long';\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.long);\n\n        // narrow\n        el.display = 'narrow';\n        el.value = expected.value;\n        await elementUpdated(el);\n        expect(el.shadowRoot?.textContent).to.equal(expected.narrow);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/format-bytes/format-bytes.ts",
    "content": "import SlFormatBytes from './format-bytes.component.js';\n\nexport * from './format-bytes.component.js';\nexport default SlFormatBytes;\n\nSlFormatBytes.define('sl-format-bytes');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-format-bytes': SlFormatBytes;\n  }\n}\n"
  },
  {
    "path": "src/components/format-date/format-date.component.ts",
    "content": "import { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property } from 'lit/decorators.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\n\n/**\n * @summary Formats a date/time using the specified locale and options.\n * @documentation https://shoelace.style/components/format-date\n * @status stable\n * @since 2.0\n */\nexport default class SlFormatDate extends ShoelaceElement {\n  private readonly localize = new LocalizeController(this);\n\n  /**\n   * The date/time to format. If not set, the current date and time will be used. When passing a string, it's strongly\n   * recommended to use the ISO 8601 format to ensure timezones are handled correctly. To convert a date to this format\n   * in JavaScript, use [`date.toISOString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString).\n   */\n  @property() date: Date | string = new Date();\n\n  /** The format for displaying the weekday. */\n  @property() weekday: 'narrow' | 'short' | 'long';\n\n  /** The format for displaying the era. */\n  @property() era: 'narrow' | 'short' | 'long';\n\n  /** The format for displaying the year. */\n  @property() year: 'numeric' | '2-digit';\n\n  /** The format for displaying the month. */\n  @property() month: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long';\n\n  /** The format for displaying the day. */\n  @property() day: 'numeric' | '2-digit';\n\n  /** The format for displaying the hour. */\n  @property() hour: 'numeric' | '2-digit';\n\n  /** The format for displaying the minute. */\n  @property() minute: 'numeric' | '2-digit';\n\n  /** The format for displaying the second. */\n  @property() second: 'numeric' | '2-digit';\n\n  /** The format for displaying the time. */\n  @property({ attribute: 'time-zone-name' }) timeZoneName: 'short' | 'long';\n\n  /** The time zone to express the time in. */\n  @property({ attribute: 'time-zone' }) timeZone: string;\n\n  /** The format for displaying the hour. */\n  @property({ attribute: 'hour-format' }) hourFormat: 'auto' | '12' | '24' = 'auto';\n\n  render() {\n    const date = new Date(this.date);\n    const hour12 = this.hourFormat === 'auto' ? undefined : this.hourFormat === '12';\n\n    // Check for an invalid date\n    if (isNaN(date.getMilliseconds())) {\n      return undefined;\n    }\n\n    return html`\n      <time datetime=${date.toISOString()}>\n        ${this.localize.date(date, {\n          weekday: this.weekday,\n          era: this.era,\n          year: this.year,\n          month: this.month,\n          day: this.day,\n          hour: this.hour,\n          minute: this.minute,\n          second: this.second,\n          timeZoneName: this.timeZoneName,\n          timeZone: this.timeZone,\n          hour12: hour12\n        })}\n      </time>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/format-date/format-date.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlFormatDate from './format-date.js';\n\ndescribe('<sl-format-date>', () => {\n  describe('defaults ', () => {\n    let clock: sinon.SinonFakeTimers;\n\n    beforeEach(() => {\n      // fake timer so `new Date()` can be tested\n      clock = sinon.useFakeTimers({\n        now: new Date()\n      });\n    });\n\n    afterEach(() => {\n      clock.restore();\n    });\n\n    it('default properties', async () => {\n      const el = await fixture<SlFormatDate>(html` <sl-format-date></sl-format-date> `);\n      expect(el.date).to.deep.equal(new Date());\n\n      expect(el.lang).to.be.undefined;\n      expect(el.weekday).to.be.undefined;\n      expect(el.era).to.be.undefined;\n      expect(el.year).to.be.undefined;\n      expect(el.month).to.be.undefined;\n      expect(el.day).to.be.undefined;\n      expect(el.hour).to.be.undefined;\n      expect(el.minute).to.be.undefined;\n      expect(el.second).to.be.undefined;\n      expect(el.timeZoneName).to.be.undefined;\n      expect(el.timeZone).to.be.undefined;\n      expect(el.hourFormat).to.equal('auto');\n    });\n  });\n\n  describe('lang property', () => {\n    const results = [\n      { lang: 'de', result: `1.1.${new Date().getFullYear()}` },\n      { lang: 'de-CH', result: `1.1.${new Date().getFullYear()}` },\n      { lang: 'fr', result: `01/01/${new Date().getFullYear()}` },\n      { lang: 'es', result: `1/1/${new Date().getFullYear()}` },\n      { lang: 'he', result: `1.1.${new Date().getFullYear()}` },\n      { lang: 'ja', result: `${new Date().getFullYear()}/1/1` },\n      { lang: 'nl', result: `1-1-${new Date().getFullYear()}` },\n      { lang: 'pl', result: `1.01.${new Date().getFullYear()}` },\n      { lang: 'pt', result: `01/01/${new Date().getFullYear()}` },\n      { lang: 'ru', result: `01.01.${new Date().getFullYear()}` }\n    ];\n    results.forEach(setup => {\n      it(`date has correct language format: ${setup.lang}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" lang=\"${setup.lang}\"></sl-format-date>\n        `);\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(setup.result);\n      });\n    });\n  });\n\n  describe('weekday property', () => {\n    const weekdays = ['narrow', 'short', 'long'];\n    weekdays.forEach((weekdayFormat: 'narrow' | 'short' | 'long') => {\n      it(`date has correct weekday format: ${weekdayFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date\n            .date=\"${new Date(new Date().getFullYear(), 0, 1)}\"\n            weekday=\"${weekdayFormat}\"\n          ></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { weekday: weekdayFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('era property', () => {\n    const eras = ['narrow', 'short', 'long'];\n    eras.forEach((eraFormat: 'narrow' | 'short' | 'long') => {\n      it(`date has correct era format: ${eraFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" era=\"${eraFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { era: eraFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('year property', () => {\n    const yearFormats = ['numeric', '2-digit'];\n    yearFormats.forEach((yearFormat: 'numeric' | '2-digit') => {\n      it(`date has correct year format: ${yearFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" year=\"${yearFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { year: yearFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('month property', () => {\n    const monthFormats = ['numeric', '2-digit', 'narrow', 'short', 'long'];\n    monthFormats.forEach((monthFormat: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long') => {\n      it(`date has correct month format: ${monthFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" month=\"${monthFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { month: monthFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('day property', () => {\n    const dayFormats = ['numeric', '2-digit'];\n    dayFormats.forEach((dayFormat: 'numeric' | '2-digit') => {\n      it(`date has correct day format: ${dayFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" day=\"${dayFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { day: dayFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('hour property', () => {\n    const hourFormats = ['numeric', '2-digit'];\n    hourFormats.forEach((hourFormat: 'numeric' | '2-digit') => {\n      it(`date has correct hour format: ${hourFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" hour=\"${hourFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { hour: hourFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('minute property', () => {\n    const minuteFormats = ['numeric', '2-digit'];\n    minuteFormats.forEach((minuteFormat: 'numeric' | '2-digit') => {\n      it(`date has correct minute format: ${minuteFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" minute=\"${minuteFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { minute: minuteFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('second property', () => {\n    const secondFormats = ['numeric', '2-digit'];\n    secondFormats.forEach((secondFormat: 'numeric' | '2-digit') => {\n      it(`date has correct second format: ${secondFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" second=\"${secondFormat}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { second: secondFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('timeZoneName property', () => {\n    const timeZoneNameFormats = ['short', 'long'];\n    timeZoneNameFormats.forEach((timeZoneNameFormat: 'short' | 'long') => {\n      it(`date has correct timeZoneName format: ${timeZoneNameFormat}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date\n            .date=\"${new Date(new Date().getFullYear(), 0, 1)}\"\n            time-zone-name=\"${timeZoneNameFormat}\"\n          ></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { timeZoneName: timeZoneNameFormat }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('timeZone property', () => {\n    const timeZones = ['America/New_York', 'America/Los_Angeles', 'Europe/Zurich'];\n    timeZones.forEach(timeZone => {\n      it(`date has correct timeZoneName format: ${timeZone}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date .date=\"${new Date(new Date().getFullYear(), 0, 1)}\" time-zone=\"${timeZone}\"></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', { timeZone: timeZone }).format(\n          new Date(new Date().getFullYear(), 0, 1)\n        );\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n\n  describe('hourFormat property', () => {\n    const hourFormatValues = ['auto', '12', '24'];\n    hourFormatValues.forEach(hourFormatValue => {\n      it(`date has correct hourFormat format: ${hourFormatValue}`, async () => {\n        const el = await fixture<SlFormatDate>(html`\n          <sl-format-date\n            .date=\"${new Date(new Date().getFullYear(), 0, 1)}\"\n            hour-format=\"${hourFormatValue as 'auto' | '12' | '24'}\"\n          ></sl-format-date>\n        `);\n\n        const expected = new Intl.DateTimeFormat('en-US', {\n          hour12: hourFormatValue === 'auto' ? undefined : hourFormatValue === '12'\n        }).format(new Date(new Date().getFullYear(), 0, 1));\n        expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/format-date/format-date.ts",
    "content": "import SlFormatDate from './format-date.component.js';\n\nexport * from './format-date.component.js';\nexport default SlFormatDate;\n\nSlFormatDate.define('sl-format-date');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-format-date': SlFormatDate;\n  }\n}\n"
  },
  {
    "path": "src/components/format-number/format-number.component.ts",
    "content": "import { LocalizeController } from '../../utilities/localize.js';\nimport { property } from 'lit/decorators.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\n\n/**\n * @summary Formats a number using the specified locale and options.\n * @documentation https://shoelace.style/components/format-number\n * @status stable\n * @since 2.0\n */\nexport default class SlFormatNumber extends ShoelaceElement {\n  private readonly localize = new LocalizeController(this);\n\n  /** The number to format. */\n  @property({ type: Number }) value = 0;\n\n  /** The formatting style to use. */\n  @property() type: 'currency' | 'decimal' | 'percent' = 'decimal';\n\n  /** Turns off grouping separators. */\n  @property({ attribute: 'no-grouping', type: Boolean }) noGrouping = false;\n\n  /** The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code to use when formatting. */\n  @property() currency = 'USD';\n\n  /** How to display the currency. */\n  @property({ attribute: 'currency-display' }) currencyDisplay: 'symbol' | 'narrowSymbol' | 'code' | 'name' = 'symbol';\n\n  /** The minimum number of integer digits to use. Possible values are 1-21. */\n  @property({ attribute: 'minimum-integer-digits', type: Number }) minimumIntegerDigits: number;\n\n  /** The minimum number of fraction digits to use. Possible values are 0-20. */\n  @property({ attribute: 'minimum-fraction-digits', type: Number }) minimumFractionDigits: number;\n\n  /** The maximum number of fraction digits to use. Possible values are 0-0. */\n  @property({ attribute: 'maximum-fraction-digits', type: Number }) maximumFractionDigits: number;\n\n  /** The minimum number of significant digits to use. Possible values are 1-21. */\n  @property({ attribute: 'minimum-significant-digits', type: Number }) minimumSignificantDigits: number;\n\n  /** The maximum number of significant digits to use,. Possible values are 1-21. */\n  @property({ attribute: 'maximum-significant-digits', type: Number }) maximumSignificantDigits: number;\n\n  render() {\n    if (isNaN(this.value)) {\n      return '';\n    }\n\n    return this.localize.number(this.value, {\n      style: this.type,\n      currency: this.currency,\n      currencyDisplay: this.currencyDisplay,\n      useGrouping: !this.noGrouping,\n      minimumIntegerDigits: this.minimumIntegerDigits,\n      minimumFractionDigits: this.minimumFractionDigits,\n      maximumFractionDigits: this.maximumFractionDigits,\n      minimumSignificantDigits: this.minimumSignificantDigits,\n      maximumSignificantDigits: this.maximumSignificantDigits\n    });\n  }\n}\n"
  },
  {
    "path": "src/components/format-number/format-number.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlFormatNumber from './format-number.js';\n\ndescribe('<sl-format-number>', () => {\n  describe('defaults ', () => {\n    it('default properties', async () => {\n      const el = await fixture<SlFormatNumber>(html` <sl-format-number></sl-format-number> `);\n      expect(el.value).to.equal(0);\n\n      expect(el.lang).to.be.undefined;\n      expect(el.type).to.equal('decimal');\n      expect(el.noGrouping).to.be.false;\n      expect(el.currency).to.equal('USD');\n      expect(el.currencyDisplay).to.equal('symbol');\n      expect(el.minimumIntegerDigits).to.be.undefined;\n      expect(el.minimumFractionDigits).to.be.undefined;\n      expect(el.maximumFractionDigits).to.be.undefined;\n      expect(el.minimumSignificantDigits).to.be.undefined;\n      expect(el.maximumSignificantDigits).to.be.undefined;\n    });\n  });\n\n  describe('lang property', () => {\n    ['de', 'de-CH', 'fr', 'es', 'he', 'ja', 'nl', 'pl', 'pt', 'ru'].forEach(lang => {\n      it(`number has correct language format: ${lang}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" lang=\"${lang}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat(lang, { style: 'decimal', useGrouping: true }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('type property', () => {\n    ['currency', 'decimal', 'percent'].forEach(type => {\n      it(`number has correct type format: ${type}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" type=\"${type}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', { style: type, currency: 'USD' }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('noGrouping property', () => {\n    it(`number has correct grouping format: no grouping`, async () => {\n      const el = await fixture<SlFormatNumber>(html` <sl-format-number value=\"1000\" no-grouping></sl-format-number> `);\n      const expected = new Intl.NumberFormat('en-US', { useGrouping: false }).format(1000);\n      expect(el.shadowRoot?.textContent).to.equal(expected);\n    });\n\n    it(`number has correct grouping format: grouping`, async () => {\n      const el = await fixture<SlFormatNumber>(html` <sl-format-number value=\"1000\"></sl-format-number> `);\n      const expected = new Intl.NumberFormat('en-US', { useGrouping: true }).format(1000);\n      expect(el.shadowRoot?.textContent).to.equal(expected);\n    });\n  });\n\n  describe('currency property', () => {\n    ['USD', 'CAD', 'AUD', 'UAH'].forEach(currency => {\n      it(`number has correct type format: ${currency}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" currency=\"${currency}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', { style: 'decimal', currency: currency }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('currencyDisplay property', () => {\n    ['symbol', 'narrowSymbol', 'code', 'name'].forEach(currencyDisplay => {\n      it(`number has correct type format: ${currencyDisplay}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" currency-display=\"${currencyDisplay}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', { style: 'decimal', currencyDisplay: currencyDisplay }).format(\n          1000\n        );\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('minimumIntegerDigits property', () => {\n    [4, 5, 6].forEach(minDigits => {\n      it(`number has correct type format: ${minDigits}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" minimum-integer-digits=\"${minDigits}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', {\n          style: 'decimal',\n          currencyDisplay: 'symbol',\n          minimumIntegerDigits: minDigits\n        }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('minimumFractionDigits property', () => {\n    [4, 5, 6].forEach(minFractionDigits => {\n      it(`number has correct type format: ${minFractionDigits}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" minimum-fraction-digits=\"${minFractionDigits}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', {\n          style: 'decimal',\n          currencyDisplay: 'symbol',\n          minimumFractionDigits: minFractionDigits\n        }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('maximumFractionDigits property', () => {\n    [4, 5, 6].forEach(maxFractionDigits => {\n      it(`number has correct type format: ${maxFractionDigits}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" maximum-fraction-digits=\"${maxFractionDigits}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', {\n          style: 'decimal',\n          currencyDisplay: 'symbol',\n          maximumFractionDigits: maxFractionDigits\n        }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('minimumSignificantDigits property', () => {\n    [4, 5, 6].forEach(minSignificantDigits => {\n      it(`number has correct type format: ${minSignificantDigits}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" minimum-significant-digits=\"${minSignificantDigits}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', {\n          style: 'decimal',\n          currencyDisplay: 'symbol',\n          minimumSignificantDigits: minSignificantDigits\n        }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n\n  describe('maximumSignificantDigits property', () => {\n    [4, 5, 6].forEach(maxSignificantDigits => {\n      it(`number has correct type format: ${maxSignificantDigits}`, async () => {\n        const el = await fixture<SlFormatNumber>(html`\n          <sl-format-number value=\"1000\" maximum-significant-digits=\"${maxSignificantDigits}\"></sl-format-number>\n        `);\n        const expected = new Intl.NumberFormat('en-US', {\n          style: 'decimal',\n          currencyDisplay: 'symbol',\n          maximumSignificantDigits: maxSignificantDigits\n        }).format(1000);\n        expect(el.shadowRoot?.textContent).to.equal(expected);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/format-number/format-number.ts",
    "content": "import SlFormatNumber from './format-number.component.js';\n\nexport * from './format-number.component.js';\nexport default SlFormatNumber;\n\nSlFormatNumber.define('sl-format-number');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-format-number': SlFormatNumber;\n  }\n}\n"
  },
  {
    "path": "src/components/icon/icon.component.ts",
    "content": "import { getIconLibrary, type IconLibrary, unwatchIcon, watchIcon } from './library.js';\nimport { html } from 'lit';\nimport { isTemplateResult } from 'lit/directive-helpers.js';\nimport { property, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './icon.styles.js';\nimport type { CSSResultGroup, HTMLTemplateResult } from 'lit';\n\nconst CACHEABLE_ERROR = Symbol();\nconst RETRYABLE_ERROR = Symbol();\ntype SVGResult = HTMLTemplateResult | SVGSVGElement | typeof RETRYABLE_ERROR | typeof CACHEABLE_ERROR;\n\nlet parser: DOMParser;\nconst iconCache = new Map<string, Promise<SVGResult>>();\n\ninterface IconSource {\n  url?: string;\n  fromLibrary: boolean;\n}\n\n/**\n * @summary Icons are symbols that can be used to represent various options within an application.\n * @documentation https://shoelace.style/components/icon\n * @status stable\n * @since 2.0\n *\n * @event sl-load - Emitted when the icon has loaded. When using `spriteSheet: true` this will not emit.\n * @event sl-error - Emitted when the icon fails to load due to an error. When using `spriteSheet: true` this will not emit.\n *\n * @csspart svg - The internal SVG element.\n * @csspart use - The <use> element generated when using `spriteSheet: true`\n */\nexport default class SlIcon extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private initialRender = false;\n\n  /** Given a URL, this function returns the resulting SVG element or an appropriate error symbol. */\n  private async resolveIcon(url: string, library?: IconLibrary): Promise<SVGResult> {\n    let fileData: Response;\n\n    if (library?.spriteSheet) {\n      this.svg = html`<svg part=\"svg\">\n        <use part=\"use\" href=\"${url}\"></use>\n      </svg>`;\n\n      return this.svg;\n    }\n\n    try {\n      fileData = await fetch(url, { mode: 'cors' });\n      if (!fileData.ok) return fileData.status === 410 ? CACHEABLE_ERROR : RETRYABLE_ERROR;\n    } catch {\n      return RETRYABLE_ERROR;\n    }\n\n    try {\n      const div = document.createElement('div');\n      div.innerHTML = await fileData.text();\n\n      const svg = div.firstElementChild;\n      if (svg?.tagName?.toLowerCase() !== 'svg') return CACHEABLE_ERROR;\n\n      if (!parser) parser = new DOMParser();\n      const doc = parser.parseFromString(svg.outerHTML, 'text/html');\n\n      const svgEl = doc.body.querySelector('svg');\n      if (!svgEl) return CACHEABLE_ERROR;\n\n      svgEl.part.add('svg');\n      return document.adoptNode(svgEl);\n    } catch {\n      return CACHEABLE_ERROR;\n    }\n  }\n\n  @state() private svg: SVGElement | HTMLTemplateResult | null = null;\n\n  /** The name of the icon to draw. Available names depend on the icon library being used. */\n  @property({ reflect: true }) name?: string;\n\n  /**\n   * An external URL of an SVG file. Be sure you trust the content you are including, as it will be executed as code and\n   * can result in XSS attacks.\n   */\n  @property() src?: string;\n\n  /**\n   * An alternate description to use for assistive devices. If omitted, the icon will be considered presentational and\n   * ignored by assistive devices.\n   */\n  @property() label = '';\n\n  /** The name of a registered custom icon library. */\n  @property({ reflect: true }) library = 'default';\n\n  connectedCallback() {\n    super.connectedCallback();\n    watchIcon(this);\n  }\n\n  firstUpdated() {\n    this.initialRender = true;\n    this.setIcon();\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    unwatchIcon(this);\n  }\n\n  private getIconSource(): IconSource {\n    const library = getIconLibrary(this.library);\n    if (this.name && library) {\n      return {\n        url: library.resolver(this.name),\n        fromLibrary: true\n      };\n    }\n\n    return {\n      url: this.src,\n      fromLibrary: false\n    };\n  }\n\n  @watch('label')\n  handleLabelChange() {\n    const hasLabel = typeof this.label === 'string' && this.label.length > 0;\n\n    if (hasLabel) {\n      this.setAttribute('role', 'img');\n      this.setAttribute('aria-label', this.label);\n      this.removeAttribute('aria-hidden');\n    } else {\n      this.removeAttribute('role');\n      this.removeAttribute('aria-label');\n      this.setAttribute('aria-hidden', 'true');\n    }\n  }\n\n  @watch(['name', 'src', 'library'])\n  async setIcon() {\n    const { url, fromLibrary } = this.getIconSource();\n    const library = fromLibrary ? getIconLibrary(this.library) : undefined;\n\n    if (!url) {\n      this.svg = null;\n      return;\n    }\n\n    let iconResolver = iconCache.get(url);\n    if (!iconResolver) {\n      iconResolver = this.resolveIcon(url, library);\n      iconCache.set(url, iconResolver);\n    }\n\n    // If we haven't rendered yet, exit early. This avoids unnecessary work due to watching multiple props.\n    if (!this.initialRender) {\n      return;\n    }\n\n    const svg = await iconResolver;\n\n    if (svg === RETRYABLE_ERROR) {\n      iconCache.delete(url);\n    }\n\n    if (url !== this.getIconSource().url) {\n      // If the url has changed while fetching the icon, ignore this request\n      return;\n    }\n\n    if (isTemplateResult(svg)) {\n      this.svg = svg;\n\n      if (library) {\n        // Using a templateResult requires the SVG to be written to the DOM first before we can grab the SVGElement\n        // to be passed to the library's mutator function.\n        await this.updateComplete;\n\n        const shadowSVG = this.shadowRoot!.querySelector(\"[part='svg']\")!;\n\n        if (typeof library.mutator === 'function' && shadowSVG) {\n          library.mutator(shadowSVG as SVGElement);\n        }\n      }\n\n      return;\n    }\n\n    switch (svg) {\n      case RETRYABLE_ERROR:\n      case CACHEABLE_ERROR:\n        this.svg = null;\n        this.emit('sl-error');\n        break;\n      default:\n        this.svg = svg.cloneNode(true) as SVGElement;\n        library?.mutator?.(this.svg);\n        this.emit('sl-load');\n    }\n  }\n\n  render() {\n    return this.svg;\n  }\n}\n"
  },
  {
    "path": "src/components/icon/icon.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n    width: 1em;\n    height: 1em;\n    box-sizing: content-box !important;\n  }\n\n  svg {\n    display: block;\n    height: 100%;\n    width: 100%;\n  }\n`;\n"
  },
  {
    "path": "src/components/icon/icon.test.ts",
    "content": "import { aTimeout, elementUpdated, expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport { registerIconLibrary } from '../../../dist/shoelace.js';\nimport type { SlErrorEvent } from '../../events/sl-error.js';\nimport type { SlLoadEvent } from '../../events/sl-load.js';\nimport type SlIcon from './icon.js';\n\nconst testLibraryIcons = {\n  'test-icon1': `\n    <svg id=\"test-icon1\">\n      <path d=\"M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z\"></path>\n    </svg>\n  `,\n  'test-icon2': `\n    <svg id=\"test-icon2\">\n    <path d=\"M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z\"></path>\n    </svg>\n  `,\n  'bad-icon': `<div></div>`\n};\n\ndescribe('<sl-icon>', () => {\n  before(() => {\n    registerIconLibrary('test-library', {\n      resolver: (name: keyof typeof testLibraryIcons) => {\n        // only for testing a bad request\n        if (name === ('bad-request' as keyof typeof testLibraryIcons)) {\n          return `data:image/svg+xml`;\n        }\n\n        if (name in testLibraryIcons) {\n          return `data:image/svg+xml,${encodeURIComponent(testLibraryIcons[name])}`;\n        }\n        return '';\n      },\n      mutator: (svg: SVGElement) => svg.setAttribute('fill', 'currentColor')\n    });\n  });\n\n  describe('defaults ', () => {\n    it('default properties', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon></sl-icon> `);\n\n      expect(el.name).to.be.undefined;\n      expect(el.src).to.be.undefined;\n      expect(el.label).to.equal('');\n      expect(el.library).to.equal('default');\n    });\n\n    it('renders pre-loaded system icons and emits sl-load event', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"system\"></sl-icon> `);\n      const listener = oneEvent(el, 'sl-load') as Promise<SlLoadEvent>;\n\n      el.name = 'check';\n      const ev = await listener;\n      await elementUpdated(el);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.exist;\n      expect(ev).to.exist;\n    });\n\n    it('the icon is accessible', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"system\" name=\"check\"></sl-icon> `);\n      await expect(el).to.be.accessible();\n    });\n\n    it('the icon has the correct default aria attributes', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"system\" name=\"check\"></sl-icon> `);\n\n      expect(el.getAttribute('role')).to.be.null;\n      expect(el.getAttribute('aria-label')).to.be.null;\n      expect(el.getAttribute('aria-hidden')).to.equal('true');\n    });\n  });\n\n  describe('when a label is provided', () => {\n    it('the icon has the correct default aria attributes', async () => {\n      const fakeLabel = 'a label';\n      const el = await fixture<SlIcon>(html` <sl-icon label=\"${fakeLabel}\" library=\"system\" name=\"check\"></sl-icon> `);\n\n      expect(el.getAttribute('role')).to.equal('img');\n      expect(el.getAttribute('aria-label')).to.equal(fakeLabel);\n      expect(el.getAttribute('aria-hidden')).to.be.null;\n    });\n  });\n\n  describe('when a valid src is provided', () => {\n    it('the svg is rendered', async () => {\n      const fakeId = 'test-src';\n      const el = await fixture<SlIcon>(html` <sl-icon></sl-icon> `);\n\n      const listener = oneEvent(el, 'sl-load');\n      el.src = `data:image/svg+xml,${encodeURIComponent(`<svg id=\"${fakeId}\"></svg>`)}`;\n\n      await listener;\n      await elementUpdated(el);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.exist;\n      expect(el.shadowRoot?.querySelector('svg')?.part.contains('svg')).to.be.true;\n      expect(el.shadowRoot?.querySelector('svg')?.getAttribute('id')).to.equal(fakeId);\n    });\n  });\n\n  describe('new library', () => {\n    it('renders icons from the new library and emits sl-load event', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"test-library\"></sl-icon> `);\n      const listener = oneEvent(el, 'sl-load') as Promise<SlLoadEvent>;\n\n      el.name = 'test-icon1';\n      const ev = await listener;\n      await elementUpdated(el);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.exist;\n      expect(ev.isTrusted).to.exist;\n    });\n\n    it('runs mutator from new library', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"test-library\" name=\"test-icon1\"></sl-icon> `);\n      await elementUpdated(el);\n\n      const svg = el.shadowRoot?.querySelector('svg');\n      expect(svg?.getAttribute('fill')).to.equal('currentColor');\n    });\n  });\n\n  describe('negative cases', () => {\n    // using new library so we can test for malformed icons when registered\n    it(\"svg not rendered with an icon that doesn't exist in the library\", async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"test-library\" name=\"does-not-exist\"></sl-icon> `);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.be.null;\n    });\n\n    it('emits sl-error when the file cant be retrieved', async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"test-library\"></sl-icon> `);\n      const listener = oneEvent(el, 'sl-error') as Promise<SlErrorEvent>;\n\n      el.name = 'bad-request';\n      const ev = await listener;\n      await elementUpdated(el);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.be.null;\n      expect(ev).to.exist;\n    });\n\n    it(\"emits sl-error when there isn't an svg element in the registered icon\", async () => {\n      const el = await fixture<SlIcon>(html` <sl-icon library=\"test-library\"></sl-icon> `);\n      const listener = oneEvent(el, 'sl-error') as Promise<SlErrorEvent>;\n\n      el.name = 'bad-icon';\n      const ev = await listener;\n      await elementUpdated(el);\n\n      expect(el.shadowRoot?.querySelector('svg')).to.be.null;\n      expect(ev).to.exist;\n    });\n  });\n\n  describe('svg sprite sheets', () => {\n    //  For some reason ESLint wants to fail in CI here, but works locally.\n    /* eslint-disable */\n    it('Should properly grab an SVG and render it from bootstrap icons', async () => {\n      registerIconLibrary('sprite', {\n        resolver: name => `/docs/assets/images/sprite.svg#${name}`,\n        mutator: svg => svg.setAttribute('fill', 'currentColor'),\n        spriteSheet: true\n      });\n\n      const el = await fixture<SlIcon>(html`<sl-icon name=\"arrow-left\" library=\"sprite\"></sl-icon>`);\n\n      await elementUpdated(el);\n\n      const svg = el.shadowRoot?.querySelector(\"svg[part='svg']\");\n      const use = svg?.querySelector(`use[href='/docs/assets/images/sprite.svg#arrow-left']`);\n\n      expect(svg).to.be.instanceof(SVGElement);\n      expect(use).to.be.instanceof(SVGUseElement);\n\n      // This is kind of hacky...but with no way to check \"load\", we just use a timeout\n      await aTimeout(1000);\n\n      // Theres no way to really test that the icon rendered properly. We just gotta trust the browser to do it's thing :)\n      // However, we can check the <use> size. It should be greater than 0x0 if loaded properly.\n      const rect = use?.getBoundingClientRect();\n      expect(rect?.width).to.be.greaterThan(0);\n      expect(rect?.width).to.be.greaterThan(0);\n    });\n\n    // https://github.com/shoelace-style/shoelace/issues/2161\n    it('Should apply mutator to multiple identical spritesheet icons', async () => {\n      registerIconLibrary('sprite', {\n        resolver: name => `/docs/assets/images/sprite.svg#${name}`,\n        mutator: svg => svg.setAttribute('fill', 'pink'),\n        spriteSheet: true\n      });\n\n      const el = await fixture<HTMLDivElement>(html`\n        <div>\n          <sl-icon name=\"arrow-left\" library=\"sprite\"></sl-icon>\n          <sl-icon name=\"arrow-left\" library=\"sprite\"></sl-icon>\n        </div>\n      `);\n\n      const icons = [...el.querySelectorAll<SlIcon>('sl-icon')];\n\n      await Promise.allSettled(icons.map(el => elementUpdated(el)));\n\n      // This is kind of hacky...but with no way to check \"load\", we just use a timeout\n      await aTimeout(1000);\n      const icon1 = icons[0];\n      const icon2 = icons[1];\n\n      expect(icon1.shadowRoot?.querySelector('svg')?.getAttribute('fill')).to.equal('pink');\n      expect(icon2.shadowRoot?.querySelector('svg')?.getAttribute('fill')).to.equal('pink');\n    });\n\n    it('Should render nothing if the sprite hash is wrong', async () => {\n      registerIconLibrary('sprite', {\n        resolver: name => `/docs/assets/images/sprite.svg#${name}`,\n        mutator: svg => svg.setAttribute('fill', 'currentColor'),\n        spriteSheet: true\n      });\n\n      const el = await fixture<SlIcon>(html`<sl-icon name=\"non-existent\" library=\"sprite\"></sl-icon>`);\n\n      await elementUpdated(el);\n\n      const svg = el.shadowRoot?.querySelector(\"svg[part='svg']\");\n      const use = svg?.querySelector('use');\n\n      // TODO: Theres no way to really test that the icon rendered properly. We just gotta trust the browser to do it's thing :)\n      // However, we can check the <use> size. If it never loaded, it should be 0x0. Ideally, we could have error tracking...\n      const rect = use?.getBoundingClientRect();\n      expect(rect?.width).to.equal(0);\n      expect(rect?.width).to.equal(0);\n\n      // Make sure the mutator is applied.\n      // https://github.com/shoelace-style/shoelace/issues/1925\n      expect(svg?.getAttribute('fill')).to.equal('currentColor');\n    });\n\n    // TODO: <use> svg icons don't emit a \"load\" or \"error\" event...if we can figure out how to get the event to emit errors.\n    // Once we figure out how to emit errors / loading perhaps we can actually test this?\n    it.skip(\"Should produce an error if the icon doesn't exist.\", async () => {\n      registerIconLibrary('sprite', {\n        resolver(name) {\n          return `/docs/assets/images/sprite.svg#${name}`;\n        },\n        mutator(svg) {\n          return svg.setAttribute('fill', 'currentColor');\n        },\n        spriteSheet: true\n      });\n\n      const el = await fixture<SlIcon>(html`<sl-icon name=\"bad-icon\" library=\"sprite\"></sl-icon>`);\n      const listener = oneEvent(el, 'sl-error') as Promise<SlErrorEvent>;\n\n      el.name = 'bad-icon';\n      const ev = await listener;\n      await elementUpdated(el);\n      expect(ev).to.exist;\n    });\n  });\n  /* eslint-enable */\n});\n"
  },
  {
    "path": "src/components/icon/icon.ts",
    "content": "import SlIcon from './icon.component.js';\n\nexport * from './icon.component.js';\nexport default SlIcon;\n\nSlIcon.define('sl-icon');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-icon': SlIcon;\n  }\n}\n"
  },
  {
    "path": "src/components/icon/library.default.ts",
    "content": "import { getBasePath } from '../../utilities/base-path.js';\nimport type { IconLibrary } from './library.js';\n\nconst library: IconLibrary = {\n  name: 'default',\n  resolver: name => getBasePath(`assets/icons/${name}.svg`)\n};\n\nexport default library;\n"
  },
  {
    "path": "src/components/icon/library.system.ts",
    "content": "import type { IconLibrary } from './library.js';\n\n//\n// System icons are a separate library to ensure they're always available, regardless of how the default icon library is\n// configured or if its icons resolve properly.\n//\n// All Shoelace components must use the system library instead of the default library. For visual consistency, system\n// icons are a subset of Bootstrap Icons.\n//\nconst icons = {\n  caret: `\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n      <polyline points=\"6 9 12 15 18 9\"></polyline>\n    </svg>\n  `,\n  check: `\n    <svg part=\"checked-icon\" class=\"checkbox__icon\" viewBox=\"0 0 16 16\">\n      <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" stroke-linecap=\"round\">\n        <g stroke=\"currentColor\">\n          <g transform=\"translate(3.428571, 3.428571)\">\n            <path d=\"M0,5.71428571 L3.42857143,9.14285714\"></path>\n            <path d=\"M9.14285714,0 L3.42857143,9.14285714\"></path>\n          </g>\n        </g>\n      </g>\n    </svg>\n  `,\n  'chevron-down': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-chevron-down\" viewBox=\"0 0 16 16\">\n      <path fill-rule=\"evenodd\" d=\"M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z\"/>\n    </svg>\n  `,\n  'chevron-left': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-chevron-left\" viewBox=\"0 0 16 16\">\n      <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n    </svg>\n  `,\n  'chevron-right': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-chevron-right\" viewBox=\"0 0 16 16\">\n      <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n    </svg>\n  `,\n  copy: `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-copy\" viewBox=\"0 0 16 16\">\n      <path fill-rule=\"evenodd\" d=\"M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2Zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6ZM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1H2Z\"/>\n    </svg>\n  `,\n  eye: `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-eye\" viewBox=\"0 0 16 16\">\n      <path d=\"M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z\"/>\n      <path d=\"M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z\"/>\n    </svg>\n  `,\n  'eye-slash': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-eye-slash\" viewBox=\"0 0 16 16\">\n      <path d=\"M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7.028 7.028 0 0 0-2.79.588l.77.771A5.944 5.944 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.134 13.134 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755-.165.165-.337.328-.517.486l.708.709z\"/>\n      <path d=\"M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829l.822.822zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829z\"/>\n      <path d=\"M3.35 5.47c-.18.16-.353.322-.518.487A13.134 13.134 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7.029 7.029 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12-.708.708z\"/>\n    </svg>\n  `,\n  eyedropper: `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-eyedropper\" viewBox=\"0 0 16 16\">\n      <path d=\"M13.354.646a1.207 1.207 0 0 0-1.708 0L8.5 3.793l-.646-.647a.5.5 0 1 0-.708.708L8.293 5l-7.147 7.146A.5.5 0 0 0 1 12.5v1.793l-.854.853a.5.5 0 1 0 .708.707L1.707 15H3.5a.5.5 0 0 0 .354-.146L11 7.707l1.146 1.147a.5.5 0 0 0 .708-.708l-.647-.646 3.147-3.146a1.207 1.207 0 0 0 0-1.708l-2-2zM2 12.707l7-7L10.293 7l-7 7H2v-1.293z\"></path>\n    </svg>\n  `,\n  'grip-vertical': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-grip-vertical\" viewBox=\"0 0 16 16\">\n      <path d=\"M7 2a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z\"></path>\n    </svg>\n  `,\n  indeterminate: `\n    <svg part=\"indeterminate-icon\" class=\"checkbox__icon\" viewBox=\"0 0 16 16\">\n      <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" stroke-linecap=\"round\">\n        <g stroke=\"currentColor\" stroke-width=\"2\">\n          <g transform=\"translate(2.285714, 6.857143)\">\n            <path d=\"M10.2857143,1.14285714 L1.14285714,1.14285714\"></path>\n          </g>\n        </g>\n      </g>\n    </svg>\n  `,\n  'person-fill': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-person-fill\" viewBox=\"0 0 16 16\">\n      <path d=\"M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z\"/>\n    </svg>\n  `,\n  'play-fill': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-play-fill\" viewBox=\"0 0 16 16\">\n      <path d=\"m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z\"></path>\n    </svg>\n  `,\n  'pause-fill': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-pause-fill\" viewBox=\"0 0 16 16\">\n      <path d=\"M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z\"></path>\n    </svg>\n  `,\n  radio: `\n    <svg part=\"checked-icon\" class=\"radio__icon\" viewBox=\"0 0 16 16\">\n      <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n        <g fill=\"currentColor\">\n          <circle cx=\"8\" cy=\"8\" r=\"3.42857143\"></circle>\n        </g>\n      </g>\n    </svg>\n  `,\n  'star-fill': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-star-fill\" viewBox=\"0 0 16 16\">\n      <path d=\"M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z\"/>\n    </svg>\n  `,\n  'x-lg': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-x-lg\" viewBox=\"0 0 16 16\">\n      <path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z\"/>\n    </svg>\n  `,\n  'x-circle-fill': `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-x-circle-fill\" viewBox=\"0 0 16 16\">\n      <path d=\"M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z\"></path>\n    </svg>\n  `\n};\n\nconst systemLibrary: IconLibrary = {\n  name: 'system',\n  resolver: (name: keyof typeof icons) => {\n    if (name in icons) {\n      return `data:image/svg+xml,${encodeURIComponent(icons[name])}`;\n    }\n    return '';\n  }\n};\n\nexport default systemLibrary;\n"
  },
  {
    "path": "src/components/icon/library.ts",
    "content": "import defaultLibrary from './library.default.js';\nimport systemLibrary from './library.system.js';\nimport type SlIcon from '../icon/icon.js';\n\nexport type IconLibraryResolver = (name: string) => string;\nexport type IconLibraryMutator = (svg: SVGElement) => void;\nexport interface IconLibrary {\n  name: string;\n  resolver: IconLibraryResolver;\n  mutator?: IconLibraryMutator;\n  spriteSheet?: boolean;\n}\n\nlet registry: IconLibrary[] = [defaultLibrary, systemLibrary];\nlet watchedIcons: SlIcon[] = [];\n\n/** Adds an icon to the list of watched icons. */\nexport function watchIcon(icon: SlIcon) {\n  watchedIcons.push(icon);\n}\n\n/** Removes an icon from the list of watched icons. */\nexport function unwatchIcon(icon: SlIcon) {\n  watchedIcons = watchedIcons.filter(el => el !== icon);\n}\n\n/** Returns a library from the registry. */\nexport function getIconLibrary(name?: string) {\n  return registry.find(lib => lib.name === name);\n}\n\n/** Adds an icon library to the registry, or overrides an existing one. */\nexport function registerIconLibrary(name: string, options: Omit<IconLibrary, 'name'>) {\n  unregisterIconLibrary(name);\n  registry.push({\n    name,\n    resolver: options.resolver,\n    mutator: options.mutator,\n    spriteSheet: options.spriteSheet\n  });\n\n  // Redraw watched icons\n  watchedIcons.forEach(icon => {\n    if (icon.library === name) {\n      icon.setIcon();\n    }\n  });\n}\n\n/** Removes an icon library from the registry. */\nexport function unregisterIconLibrary(name: string) {\n  registry = registry.filter(lib => lib.name !== name);\n}\n"
  },
  {
    "path": "src/components/icon-button/icon-button.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html, literal } from 'lit/static-html.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './icon-button.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.\n * @documentation https://shoelace.style/components/icon-button\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @event sl-blur - Emitted when the icon button loses focus.\n * @event sl-focus - Emitted when the icon button gains focus.\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlIconButton extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  @query('.icon-button') button: HTMLButtonElement | HTMLLinkElement;\n\n  @state() private hasFocus = false;\n\n  /** The name of the icon to draw. Available names depend on the icon library being used. */\n  @property() name?: string;\n\n  /** The name of a registered custom icon library. */\n  @property() library?: string;\n\n  /**\n   * An external URL of an SVG file. Be sure you trust the content you are including, as it will be executed as code and\n   * can result in XSS attacks.\n   */\n  @property() src?: string;\n\n  /** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */\n  @property() href?: string;\n\n  /** Tells the browser where to open the link. Only used when `href` is set. */\n  @property() target?: '_blank' | '_parent' | '_self' | '_top';\n\n  /** Tells the browser to download the linked file as this filename. Only used when `href` is set. */\n  @property() download?: string;\n\n  /**\n   * A description that gets read by assistive devices. For optimal accessibility, you should always include a label\n   * that describes what the icon button does.\n   */\n  @property() label = '';\n\n  /** Disables the button. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  private handleClick(event: MouseEvent) {\n    if (this.disabled) {\n      event.preventDefault();\n      event.stopPropagation();\n    }\n  }\n\n  /** Simulates a click on the icon button. */\n  click() {\n    this.button.click();\n  }\n\n  /** Sets focus on the icon button. */\n  focus(options?: FocusOptions) {\n    this.button.focus(options);\n  }\n\n  /** Removes focus from the icon button. */\n  blur() {\n    this.button.blur();\n  }\n\n  render() {\n    const isLink = this.href ? true : false;\n    const tag = isLink ? literal`a` : literal`button`;\n\n    /* eslint-disable lit/binding-positions, lit/no-invalid-html */\n    return html`\n      <${tag}\n        part=\"base\"\n        class=${classMap({\n          'icon-button': true,\n          'icon-button--disabled': !isLink && this.disabled,\n          'icon-button--focused': this.hasFocus\n        })}\n        ?disabled=${ifDefined(isLink ? undefined : this.disabled)}\n        type=${ifDefined(isLink ? undefined : 'button')}\n        href=${ifDefined(isLink ? this.href : undefined)}\n        target=${ifDefined(isLink ? this.target : undefined)}\n        download=${ifDefined(isLink ? this.download : undefined)}\n        rel=${ifDefined(isLink && this.target ? 'noreferrer noopener' : undefined)}\n        role=${ifDefined(isLink ? undefined : 'button')}\n        aria-disabled=${this.disabled ? 'true' : 'false'}\n        aria-label=\"${this.label}\"\n        tabindex=${this.disabled ? '-1' : '0'}\n        @blur=${this.handleBlur}\n        @focus=${this.handleFocus}\n        @click=${this.handleClick}\n      >\n        <sl-icon\n          class=\"icon-button__icon\"\n          name=${ifDefined(this.name)}\n          library=${ifDefined(this.library)}\n          src=${ifDefined(this.src)}\n          aria-hidden=\"true\"\n        ></sl-icon>\n      </${tag}>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/icon-button/icon-button.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n    color: var(--sl-color-neutral-600);\n  }\n\n  .icon-button {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    background: none;\n    border: none;\n    border-radius: var(--sl-border-radius-medium);\n    font-size: inherit;\n    color: inherit;\n    padding: var(--sl-spacing-x-small);\n    cursor: pointer;\n    transition: var(--sl-transition-x-fast) color;\n    -webkit-appearance: none;\n  }\n\n  .icon-button:hover:not(.icon-button--disabled),\n  .icon-button:focus-visible:not(.icon-button--disabled) {\n    color: var(--sl-color-primary-600);\n  }\n\n  .icon-button:active:not(.icon-button--disabled) {\n    color: var(--sl-color-primary-700);\n  }\n\n  .icon-button:focus {\n    outline: none;\n  }\n\n  .icon-button--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .icon-button:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .icon-button__icon {\n    pointer-events: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/icon-button/icon-button.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlIconButton from './icon-button.js';\n\ntype LinkTarget = '_self' | '_blank' | '_parent' | '_top';\n\ndescribe('<sl-icon-button>', () => {\n  describe('defaults ', () => {\n    it('default properties', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button></sl-icon-button> `);\n\n      expect(el.name).to.be.undefined;\n      expect(el.library).to.be.undefined;\n      expect(el.src).to.be.undefined;\n      expect(el.href).to.be.undefined;\n      expect(el.target).to.be.undefined;\n      expect(el.download).to.be.undefined;\n      expect(el.label).to.equal('');\n      expect(el.disabled).to.equal(false);\n    });\n\n    it('renders as a button by default', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button></sl-icon-button> `);\n\n      expect(el.shadowRoot?.querySelector('button')).to.exist;\n      expect(el.shadowRoot?.querySelector('a')).not.to.exist;\n    });\n  });\n\n  describe('when styling the host element', () => {\n    it('renders the correct color and font size', async () => {\n      const el = await fixture<SlIconButton>(html`\n        <sl-icon-button\n          library=\"system\"\n          name=\"check\"\n          style=\"color: rgb(0, 136, 221); font-size: 2rem;\"\n        ></sl-icon-button>\n      `);\n      const icon = el.shadowRoot!.querySelector('sl-icon')!;\n      const styles = getComputedStyle(icon);\n\n      expect(styles.color).to.equal('rgb(0, 136, 221)');\n      expect(styles.fontSize).to.equal('32px');\n    });\n  });\n\n  describe('when icon attributes are present', () => {\n    it('renders an sl-icon from a library', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button library=\"system\" name=\"check\"></sl-icon-button> `);\n      expect(el.shadowRoot?.querySelector('sl-icon')).to.exist;\n    });\n\n    it('renders an sl-icon from a src', async () => {\n      const fakeId = 'test-src';\n      const el = await fixture<SlIconButton>(html` <sl-icon-button></sl-icon-button> `);\n\n      el.src = `data:image/svg+xml,${encodeURIComponent(`<svg id=\"${fakeId}\"></svg>`)}`;\n\n      const internalSlIcon = el.shadowRoot?.querySelector('sl-icon');\n\n      await waitUntil(() => internalSlIcon?.shadowRoot?.querySelector('svg'), 'SVG not rendered');\n\n      expect(internalSlIcon).to.exist;\n      expect(internalSlIcon?.shadowRoot?.querySelector('svg')).to.exist;\n      expect(internalSlIcon?.shadowRoot?.querySelector('svg')?.getAttribute('id')).to.equal(fakeId);\n    });\n  });\n\n  describe('when href is present', () => {\n    it('renders as an anchor', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button href=\"some/path\"></sl-icon-button> `);\n\n      expect(el.shadowRoot?.querySelector('a')).to.exist;\n      expect(el.shadowRoot?.querySelector('button')).not.to.exist;\n    });\n\n    it(`the anchor rel is not present`, async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button href=\"some/path\"></sl-icon-button> `);\n      expect(el.shadowRoot?.querySelector(`a[rel]`)).not.to.exist;\n    });\n\n    describe('and target is present', () => {\n      ['_blank', '_parent', '_self', '_top'].forEach((target: LinkTarget) => {\n        it(`the anchor target is the provided target: ${target}`, async () => {\n          const el = await fixture<SlIconButton>(html`\n            <sl-icon-button href=\"some/path\" target=\"${target}\"></sl-icon-button>\n          `);\n          expect(el.shadowRoot?.querySelector(`a[target=\"${target}\"]`)).to.exist;\n        });\n\n        it(`the anchor rel is set to 'noreferrer noopener'`, async () => {\n          const el = await fixture<SlIconButton>(html`\n            <sl-icon-button href=\"some/path\" target=\"${target}\"></sl-icon-button>\n          `);\n          expect(el.shadowRoot?.querySelector(`a[rel=\"noreferrer noopener\"]`)).to.exist;\n        });\n      });\n    });\n\n    describe('and download is present', () => {\n      it(`the anchor download attribute is the provided download`, async () => {\n        const fakeDownload = 'some/path';\n        const el = await fixture<SlIconButton>(html`\n          <sl-icon-button href=\"some/path\" download=\"${fakeDownload}\"></sl-icon-button>\n        `);\n\n        expect(el.shadowRoot?.querySelector(`a[download=\"${fakeDownload}\"]`)).to.exist;\n      });\n    });\n  });\n\n  describe('when label is present', () => {\n    it('the internal aria-label attribute is set to the provided label when rendering a button', async () => {\n      const fakeLabel = 'some label';\n      const el = await fixture<SlIconButton>(html` <sl-icon-button label=\"${fakeLabel}\"></sl-icon-button> `);\n      expect(el.shadowRoot?.querySelector(`button[aria-label=\"${fakeLabel}\"]`)).to.exist;\n    });\n\n    it('the internal aria-label attribute is set to the provided label when rendering an anchor', async () => {\n      const fakeLabel = 'some label';\n      const el = await fixture<SlIconButton>(html`\n        <sl-icon-button href=\"some/path\" label=\"${fakeLabel}\"></sl-icon-button>\n      `);\n      expect(el.shadowRoot?.querySelector(`a[aria-label=\"${fakeLabel}\"]`)).to.exist;\n    });\n  });\n\n  describe('when disabled is present', () => {\n    it('the internal button has a disabled attribute when rendering a button', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button disabled></sl-icon-button> `);\n      expect(el.shadowRoot?.querySelector(`button[disabled]`)).to.exist;\n    });\n\n    it('the internal anchor has an aria-disabled attribute when rendering an anchor', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button href=\"some/path\" disabled></sl-icon-button> `);\n      expect(el.shadowRoot?.querySelector(`a[aria-disabled=\"true\"]`)).to.exist;\n    });\n  });\n\n  describe('when using methods', () => {\n    it('should emit sl-focus and sl-blur when the button is focused and blurred', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button></sl-icon-button> `);\n      const focusHandler = sinon.spy();\n      const blurHandler = sinon.spy();\n\n      el.addEventListener('sl-focus', focusHandler);\n      el.addEventListener('sl-blur', blurHandler);\n\n      el.focus();\n      await waitUntil(() => focusHandler.calledOnce);\n\n      el.blur();\n      await waitUntil(() => blurHandler.calledOnce);\n\n      expect(focusHandler).to.have.been.calledOnce;\n      expect(blurHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit a click event when calling click()', async () => {\n      const el = await fixture<SlIconButton>(html` <sl-icon-button></sl-icon-button> `);\n      const clickHandler = sinon.spy();\n\n      el.addEventListener('click', clickHandler);\n      el.click();\n      await waitUntil(() => clickHandler.calledOnce);\n\n      expect(clickHandler).to.have.been.calledOnce;\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/icon-button/icon-button.ts",
    "content": "import SlIconButton from './icon-button.component.js';\n\nexport * from './icon-button.component.js';\nexport default SlIconButton;\n\nSlIconButton.define('sl-icon-button');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-icon-button': SlIconButton;\n  }\n}\n"
  },
  {
    "path": "src/components/image-comparer/image-comparer.component.ts",
    "content": "import { clamp } from '../../internal/math.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { drag } from '../../internal/drag.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './image-comparer.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Compare visual differences between similar photos with a sliding panel.\n * @documentation https://shoelace.style/components/image-comparer\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot before - The before image, an `<img>` or `<svg>` element.\n * @slot after - The after image, an `<img>` or `<svg>` element.\n * @slot handle - The icon used inside the handle.\n *\n * @event sl-change - Emitted when the position changes.\n *\n * @csspart base - The component's base wrapper.\n * @csspart before - The container that wraps the before image.\n * @csspart after - The container that wraps the after image.\n * @csspart divider - The divider that separates the images.\n * @csspart handle - The handle that the user drags to expose the after image.\n *\n * @cssproperty --divider-width - The width of the dividing line.\n * @cssproperty --handle-size - The size of the compare handle.\n */\nexport default class SlImageComparer extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static scopedElement = { 'sl-icon': SlIcon };\n\n  private readonly localize = new LocalizeController(this);\n\n  @query('.image-comparer') base: HTMLElement;\n  @query('.image-comparer__handle') handle: HTMLElement;\n\n  /** The position of the divider as a percentage. */\n  @property({ type: Number, reflect: true }) position = 50;\n\n  private handleDrag(event: PointerEvent) {\n    const { width } = this.base.getBoundingClientRect();\n    const isRtl = this.localize.dir() === 'rtl';\n\n    event.preventDefault();\n\n    drag(this.base, {\n      onMove: x => {\n        this.position = parseFloat(clamp((x / width) * 100, 0, 100).toFixed(2));\n        if (isRtl) this.position = 100 - this.position;\n      },\n      initialEvent: event\n    });\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    const isLtr = this.localize.dir() === 'ltr';\n    const isRtl = this.localize.dir() === 'rtl';\n\n    if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {\n      const incr = event.shiftKey ? 10 : 1;\n      let newPosition = this.position;\n\n      event.preventDefault();\n\n      if ((isLtr && event.key === 'ArrowLeft') || (isRtl && event.key === 'ArrowRight')) {\n        newPosition -= incr;\n      }\n      if ((isLtr && event.key === 'ArrowRight') || (isRtl && event.key === 'ArrowLeft')) {\n        newPosition += incr;\n      }\n      if (event.key === 'Home') {\n        newPosition = 0;\n      }\n      if (event.key === 'End') {\n        newPosition = 100;\n      }\n      newPosition = clamp(newPosition, 0, 100);\n\n      this.position = newPosition;\n    }\n  }\n\n  @watch('position', { waitUntilFirstUpdate: true })\n  handlePositionChange() {\n    this.emit('sl-change');\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n\n    return html`\n      <div\n        part=\"base\"\n        id=\"image-comparer\"\n        class=${classMap({\n          'image-comparer': true,\n          'image-comparer--rtl': isRtl\n        })}\n        @keydown=${this.handleKeyDown}\n      >\n        <div class=\"image-comparer__image\">\n          <div part=\"before\" class=\"image-comparer__before\">\n            <slot name=\"before\"></slot>\n          </div>\n\n          <div\n            part=\"after\"\n            class=\"image-comparer__after\"\n            style=${styleMap({\n              clipPath: isRtl ? `inset(0 0 0 ${100 - this.position}%)` : `inset(0 ${100 - this.position}% 0 0)`\n            })}\n          >\n            <slot name=\"after\"></slot>\n          </div>\n        </div>\n\n        <div\n          part=\"divider\"\n          class=\"image-comparer__divider\"\n          style=${styleMap({\n            left: isRtl ? `${100 - this.position}%` : `${this.position}%`\n          })}\n          @mousedown=${this.handleDrag}\n          @touchstart=${this.handleDrag}\n        >\n          <div\n            part=\"handle\"\n            class=\"image-comparer__handle\"\n            role=\"scrollbar\"\n            aria-valuenow=${this.position}\n            aria-valuemin=\"0\"\n            aria-valuemax=\"100\"\n            aria-controls=\"image-comparer\"\n            tabindex=\"0\"\n          >\n            <slot name=\"handle\">\n              <sl-icon library=\"system\" name=\"grip-vertical\"></sl-icon>\n            </slot>\n          </div>\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/image-comparer/image-comparer.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --divider-width: 2px;\n    --handle-size: 2.5rem;\n\n    display: inline-block;\n    position: relative;\n  }\n\n  .image-comparer {\n    max-width: 100%;\n    max-height: 100%;\n    overflow: hidden;\n  }\n\n  .image-comparer__before,\n  .image-comparer__after {\n    display: block;\n    pointer-events: none;\n  }\n\n  .image-comparer__before::slotted(img),\n  .image-comparer__after::slotted(img),\n  .image-comparer__before::slotted(svg),\n  .image-comparer__after::slotted(svg) {\n    display: block;\n    max-width: 100% !important;\n    height: auto;\n  }\n\n  .image-comparer__after {\n    position: absolute;\n    top: 0;\n    left: 0;\n    height: 100%;\n    width: 100%;\n  }\n\n  .image-comparer__divider {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: absolute;\n    top: 0;\n    width: var(--divider-width);\n    height: 100%;\n    background-color: var(--sl-color-neutral-0);\n    translate: calc(var(--divider-width) / -2);\n    cursor: ew-resize;\n  }\n\n  .image-comparer__handle {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: absolute;\n    top: calc(50% - (var(--handle-size) / 2));\n    width: var(--handle-size);\n    height: var(--handle-size);\n    background-color: var(--sl-color-neutral-0);\n    border-radius: var(--sl-border-radius-circle);\n    font-size: calc(var(--handle-size) * 0.5);\n    color: var(--sl-color-neutral-700);\n    cursor: inherit;\n    z-index: 10;\n  }\n\n  .image-comparer__handle:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n`;\n"
  },
  {
    "path": "src/components/image-comparer/image-comparer.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlImageComparer from './image-comparer.js';\n\ndescribe('<sl-image-comparer>', () => {\n  it('should render a basic before/after', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const afterPart = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"after\"]')!;\n    const iconContainer = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=\"handle\"]')!;\n    const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"handle\"]')!;\n\n    expect(el.position).to.equal(50);\n    expect(afterPart.getAttribute('style')).to.equal('clip-path:inset(0 50% 0 0);');\n    expect(iconContainer.assignedElements().length).to.equal(0);\n    expect(handle.getAttribute('role')).to.equal('scrollbar');\n    expect(handle.getAttribute('aria-valuenow')).to.equal('50');\n    expect(handle.getAttribute('aria-valuemin')).to.equal('0');\n    expect(handle.getAttribute('aria-valuemax')).to.equal('100');\n  });\n\n  it('should emit change event when position changed manually', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n    const handler = sinon.spy();\n\n    el.addEventListener('sl-change', handler, { once: true });\n\n    el.position = 40;\n    await el.updateComplete;\n\n    expect(handler.called).to.equal(true);\n  });\n\n  it('should increment position on arrow right', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowRight'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(51);\n  });\n\n  it('should decrement position on arrow left', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowLeft'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(49);\n  });\n\n  it('should set position to 0 on home key', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'Home'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(0);\n  });\n\n  it('should set position to 100 on end key', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'End'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(100);\n  });\n\n  it('should clamp to 100 on arrow right', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    el.position = 0;\n    await el.updateComplete;\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowLeft'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(0);\n  });\n\n  it('should clamp to 0 on arrow left', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    el.position = 100;\n    await el.updateComplete;\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowRight'\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(100);\n  });\n\n  it('should increment position by 10 on arrow right + shift', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowRight',\n        shiftKey: true\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(60);\n  });\n\n  it('should decrement position by 10 on arrow left + shift', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    base.dispatchEvent(\n      new KeyboardEvent('keydown', {\n        key: 'ArrowLeft',\n        shiftKey: true\n      })\n    );\n    await el.updateComplete;\n\n    expect(el.position).to.equal(40);\n  });\n\n  it('should set position by attribute', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer position=\"10\">\n        <div slot=\"before\"></div>\n        <div slot=\"after\"></div>\n      </sl-image-comparer>\n    `);\n\n    expect(el.position).to.equal(10);\n  });\n\n  it('should move position on drag', async () => {\n    const el = await fixture<SlImageComparer>(html`\n      <sl-image-comparer>\n        <div slot=\"before\" style=\"width: 50px\"></div>\n        <div slot=\"after\" style=\"width: 50px\"></div>\n      </sl-image-comparer>\n    `);\n    const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"handle\"]')!;\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const rect = base.getBoundingClientRect();\n    const offsetX = rect.left + window.scrollX;\n    const offsetY = rect.top + window.scrollY;\n\n    handle.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));\n\n    document.dispatchEvent(\n      new PointerEvent('pointermove', {\n        clientX: offsetX + 20,\n        clientY: offsetY\n      })\n    );\n\n    document.dispatchEvent(new PointerEvent('pointerup'));\n\n    await el.updateComplete;\n\n    expect(el.position).to.equal(40);\n  });\n});\n"
  },
  {
    "path": "src/components/image-comparer/image-comparer.ts",
    "content": "import SlImageComparer from './image-comparer.component.js';\n\nexport * from './image-comparer.component.js';\nexport default SlImageComparer;\n\nSlImageComparer.define('sl-image-comparer');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-image-comparer': SlImageComparer;\n  }\n}\n"
  },
  {
    "path": "src/components/include/include.component.ts",
    "content": "import { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { requestInclude } from './request.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './include.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Includes give you the power to embed external HTML files into the page.\n * @documentation https://shoelace.style/components/include\n * @status stable\n * @since 2.0\n *\n * @event sl-load - Emitted when the included file is loaded.\n * @event {{ status: number }} sl-error - Emitted when the included file fails to load due to an error.\n */\nexport default class SlInclude extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  /**\n   * The location of the HTML file to include. Be sure you trust the content you are including as it will be executed as\n   * code and can result in XSS attacks.\n   */\n  @property() src: string;\n\n  /** The fetch mode to use. */\n  @property() mode: 'cors' | 'no-cors' | 'same-origin' = 'cors';\n\n  /**\n   * Allows included scripts to be executed. Be sure you trust the content you are including as it will be executed as\n   * code and can result in XSS attacks.\n   */\n  @property({ attribute: 'allow-scripts', type: Boolean }) allowScripts = false;\n\n  private executeScript(script: HTMLScriptElement) {\n    // Create a copy of the script and swap it out so the browser executes it\n    const newScript = document.createElement('script');\n    [...script.attributes].forEach(attr => newScript.setAttribute(attr.name, attr.value));\n    newScript.textContent = script.textContent;\n    script.parentNode!.replaceChild(newScript, script);\n  }\n\n  @watch('src')\n  async handleSrcChange() {\n    try {\n      const src = this.src;\n      const file = await requestInclude(src, this.mode);\n\n      // If the src changed since the request started do nothing, otherwise we risk overwriting a subsequent response\n      if (src !== this.src) {\n        return;\n      }\n\n      if (!file.ok) {\n        this.emit('sl-error', { detail: { status: file.status } });\n        return;\n      }\n\n      this.innerHTML = file.html;\n\n      if (this.allowScripts) {\n        [...this.querySelectorAll('script')].forEach(script => this.executeScript(script));\n      }\n\n      this.emit('sl-load');\n    } catch {\n      this.emit('sl-error', { detail: { status: -1 } });\n    }\n  }\n\n  render() {\n    return html`<slot></slot>`;\n  }\n}\n"
  },
  {
    "path": "src/components/include/include.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n`;\n"
  },
  {
    "path": "src/components/include/include.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlInclude from './include.js';\n\nconst stubbedFetchResponse: Response = {\n  headers: new Headers(),\n  ok: true,\n  redirected: false,\n  status: 200,\n  statusText: 'OK',\n  type: 'default',\n  url: '',\n  json: () => Promise.resolve({}),\n  text: () => Promise.resolve(''),\n  blob: sinon.fake(),\n  arrayBuffer: sinon.fake(),\n  formData: sinon.fake(),\n  bodyUsed: false,\n  body: null,\n  clone: sinon.fake()\n};\n\nasync function delayResolve(resolveValue: string) {\n  // Delay the fetch response to give time for the event listener to attach\n  await aTimeout(10);\n  return resolveValue;\n}\n\ndescribe('<sl-include>', () => {\n  afterEach(() => {\n    sinon.verifyAndRestore();\n  });\n\n  it('should load content and emit sl-load', async () => {\n    sinon.stub(window, 'fetch').resolves({\n      ...stubbedFetchResponse,\n      ok: true,\n      status: 200,\n      text: () => delayResolve('\"id\": 1')\n    });\n    const el = await fixture<SlInclude>(html` <sl-include src=\"/found\"></sl-include> `);\n    const loadHandler = sinon.spy();\n\n    el.addEventListener('sl-load', loadHandler);\n    await waitUntil(() => loadHandler.calledOnce);\n\n    expect(el.innerHTML).to.contain('\"id\": 1');\n    expect(loadHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-error when content cannot be loaded', async () => {\n    sinon.stub(window, 'fetch').resolves({\n      ...stubbedFetchResponse,\n      ok: false,\n      status: 404,\n      text: () => delayResolve('{}')\n    });\n    const el = await fixture<SlInclude>(html` <sl-include src=\"/not-found\"></sl-include> `);\n    const loadHandler = sinon.spy();\n\n    el.addEventListener('sl-error', loadHandler);\n    await waitUntil(() => loadHandler.calledOnce);\n\n    expect(loadHandler).to.have.been.calledOnce;\n  });\n});\n"
  },
  {
    "path": "src/components/include/include.ts",
    "content": "import SlInclude from './include.component.js';\n\nexport * from './include.component.js';\nexport default SlInclude;\n\nSlInclude.define('sl-include');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-include': SlInclude;\n  }\n}\n"
  },
  {
    "path": "src/components/include/request.ts",
    "content": "interface IncludeFile {\n  ok: boolean;\n  status: number;\n  html: string;\n}\n\nconst includeFiles = new Map<string, IncludeFile | Promise<IncludeFile>>();\n\n/** Fetches an include file from a remote source. Caching is enabled so the origin is only pinged once. */\nexport function requestInclude(src: string, mode: 'cors' | 'no-cors' | 'same-origin' = 'cors'): Promise<IncludeFile> {\n  const prev = includeFiles.get(src);\n  if (prev !== undefined) {\n    // Promise.resolve() transparently unboxes prev if it was a promise.\n    return Promise.resolve(prev);\n  }\n  const fileDataPromise = fetch(src, { mode: mode }).then(async response => {\n    const res = {\n      ok: response.ok,\n      status: response.status,\n      html: await response.text()\n    };\n    // Replace the cached promise with its result to avoid having buggy browser Promises retain memory as mentioned in #1284 and #1249\n    includeFiles.set(src, res);\n    return res;\n  });\n  // Cache the promise to only fetch() once per src\n  includeFiles.set(src, fileDataPromise);\n  return fileDataPromise;\n}\n"
  },
  {
    "path": "src/components/input/input.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './input.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Inputs collect data from the user.\n * @documentation https://shoelace.style/components/input\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot label - The input's label. Alternatively, you can use the `label` attribute.\n * @slot prefix - Used to prepend a presentational icon or similar element to the input.\n * @slot suffix - Used to append a presentational icon or similar element to the input.\n * @slot clear-icon - An icon to use in lieu of the default clear icon.\n * @slot show-password-icon - An icon to use in lieu of the default show password icon.\n * @slot hide-password-icon - An icon to use in lieu of the default hide password icon.\n * @slot help-text - Text that describes how to use the input. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-change - Emitted when an alteration to the control's value is committed by the user.\n * @event sl-clear - Emitted when the clear button is activated.\n * @event sl-focus - Emitted when the control gains focus.\n * @event sl-input - Emitted when the control receives input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart form-control - The form control that wraps the label, input, and help text.\n * @csspart form-control-label - The label's wrapper.\n * @csspart form-control-input - The input's wrapper.\n * @csspart form-control-help-text - The help text's wrapper.\n * @csspart base - The component's base wrapper.\n * @csspart input - The internal `<input>` control.\n * @csspart prefix - The container that wraps the prefix.\n * @csspart clear-button - The clear button.\n * @csspart password-toggle-button - The password toggle button.\n * @csspart suffix - The container that wraps the suffix.\n */\nexport default class SlInput extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  private readonly formControlController = new FormControlController(this, {\n    assumeInteractionOn: ['sl-blur', 'sl-input']\n  });\n  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');\n  private readonly localize = new LocalizeController(this);\n\n  @query('.input__control') input: HTMLInputElement;\n\n  @state() private hasFocus = false;\n  @property() title = ''; // make reactive to pass through\n\n  private __numberInput = Object.assign(document.createElement('input'), { type: 'number' });\n  private __dateInput = Object.assign(document.createElement('input'), { type: 'date' });\n\n  /**\n   * The type of input. Works the same as a native `<input>` element, but only a subset of types are supported. Defaults\n   * to `text`.\n   */\n  @property({ reflect: true }) type:\n    | 'date'\n    | 'datetime-local'\n    | 'email'\n    | 'number'\n    | 'password'\n    | 'search'\n    | 'tel'\n    | 'text'\n    | 'time'\n    | 'url' = 'text';\n\n  /** The name of the input, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** The current value of the input, submitted as a name/value pair with form data. */\n  @property() value = '';\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue() defaultValue = '';\n\n  /** The input's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Draws a filled input. */\n  @property({ type: Boolean, reflect: true }) filled = false;\n\n  /** Draws a pill-style input with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  /** The input's label. If you need to display HTML, use the `label` slot instead. */\n  @property() label = '';\n\n  /** The input's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** Adds a clear button when the input is not empty. */\n  @property({ type: Boolean }) clearable = false;\n\n  /** Disables the input. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Placeholder text to show as a hint when the input is empty. */\n  @property() placeholder = '';\n\n  /** Makes the input readonly. */\n  @property({ type: Boolean, reflect: true }) readonly = false;\n\n  /** Adds a button to toggle the password's visibility. Only applies to password types. */\n  @property({ attribute: 'password-toggle', type: Boolean }) passwordToggle = false;\n\n  /** Determines whether or not the password is currently visible. Only applies to password input types. */\n  @property({ attribute: 'password-visible', type: Boolean }) passwordVisible = false;\n\n  /** Hides the browser's built-in increment/decrement spin buttons for number inputs. */\n  @property({ attribute: 'no-spin-buttons', type: Boolean }) noSpinButtons = false;\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Makes the input a required field. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** A regular expression pattern to validate input against. */\n  @property() pattern: string;\n\n  /** The minimum length of input that will be considered valid. */\n  @property({ type: Number }) minlength: number;\n\n  /** The maximum length of input that will be considered valid. */\n  @property({ type: Number }) maxlength: number;\n\n  /** The input's minimum value. Only applies to date and number input types. */\n  @property() min: number | string;\n\n  /** The input's maximum value. Only applies to date and number input types. */\n  @property() max: number | string;\n\n  /**\n   * Specifies the granularity that the value must adhere to, or the special value `any` which means no stepping is\n   * implied, allowing any numeric value. Only applies to date and number input types.\n   */\n  @property() step: number | 'any';\n\n  /** Controls whether and how text input is automatically capitalized as it is entered by the user. */\n  @property() autocapitalize: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';\n\n  /** Indicates whether the browser's autocorrect feature is on or off. */\n  @property() autocorrect: 'off' | 'on';\n\n  /**\n   * Specifies what permission the browser has to provide assistance in filling out form field values. Refer to\n   * [this page on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for available values.\n   */\n  @property() autocomplete: string;\n\n  /** Indicates that the input should receive focus on page load. */\n  @property({ type: Boolean }) autofocus: boolean;\n\n  /** Used to customize the label or icon of the Enter key on virtual keyboards. */\n  @property() enterkeyhint: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';\n\n  /** Enables spell checking on the input. */\n  @property({\n    type: Boolean,\n    converter: {\n      // Allow \"true|false\" attribute values but keep the property boolean\n      fromAttribute: value => (!value || value === 'false' ? false : true),\n      toAttribute: value => (value ? 'true' : 'false')\n    }\n  })\n  spellcheck = true;\n\n  /**\n   * Tells the browser what type of data will be entered by the user, allowing it to display the appropriate virtual\n   * keyboard on supportive devices.\n   */\n  @property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';\n\n  //\n  // NOTE: We use an in-memory input for these getters/setters instead of the one in the template because the properties\n  // can be set before the component is rendered.\n  //\n\n  /**\n   * Gets or sets the current value as a `Date` object. Returns `null` if the value can't be converted. This will use the native `<input type=\"{{type}}\">` implementation and may result in an error.\n   */\n  get valueAsDate() {\n    this.__dateInput.type = this.type;\n    this.__dateInput.value = this.value;\n    return this.input?.valueAsDate || this.__dateInput.valueAsDate;\n  }\n\n  set valueAsDate(newValue: Date | null) {\n    this.__dateInput.type = this.type;\n    this.__dateInput.valueAsDate = newValue;\n    this.value = this.__dateInput.value;\n  }\n\n  /** Gets or sets the current value as a number. Returns `NaN` if the value can't be converted. */\n  get valueAsNumber() {\n    this.__numberInput.value = this.value;\n    return this.input?.valueAsNumber || this.__numberInput.valueAsNumber;\n  }\n\n  set valueAsNumber(newValue: number) {\n    this.__numberInput.valueAsNumber = newValue;\n    this.value = this.__numberInput.value;\n  }\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  firstUpdated() {\n    this.formControlController.updateValidity();\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleChange() {\n    this.value = this.input.value;\n    this.emit('sl-change');\n  }\n\n  private handleClearClick(event: MouseEvent) {\n    event.preventDefault();\n\n    if (this.value !== '') {\n      this.value = '';\n      this.emit('sl-clear');\n      this.emit('sl-input');\n      this.emit('sl-change');\n    }\n\n    this.input.focus();\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  private handleInput() {\n    this.value = this.input.value;\n    this.formControlController.updateValidity();\n    this.emit('sl-input');\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    const hasModifier = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;\n\n    // Pressing enter when focused on an input should submit the form like a native input, but we wait a tick before\n    // submitting to allow users to cancel the keydown event if they need to\n    if (event.key === 'Enter' && !hasModifier) {\n      setTimeout(() => {\n        //\n        // When using an Input Method Editor (IME), pressing enter will cause the form to submit unexpectedly. One way\n        // to check for this is to look at event.isComposing, which will be true when the IME is open.\n        //\n        // See https://github.com/shoelace-style/shoelace/pull/988\n        //\n        if (!event.defaultPrevented && !event.isComposing) {\n          this.formControlController.submit();\n        }\n      });\n    }\n  }\n\n  private handlePasswordToggle() {\n    this.passwordVisible = !this.passwordVisible;\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Disabled form controls are always valid\n    this.formControlController.setValidity(this.disabled);\n  }\n\n  @watch('step', { waitUntilFirstUpdate: true })\n  handleStepChange() {\n    // If step changes, the value may become invalid so we need to recheck after the update. We set the new step\n    // imperatively so we don't have to wait for the next render to report the updated validity.\n    this.input.step = String(this.step);\n    this.formControlController.updateValidity();\n  }\n\n  @watch('value', { waitUntilFirstUpdate: true })\n  async handleValueChange() {\n    await this.updateComplete;\n    this.formControlController.updateValidity();\n  }\n\n  /** Sets focus on the input. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the input. */\n  blur() {\n    this.input.blur();\n  }\n\n  /** Selects all the text in the input. */\n  select() {\n    this.input.select();\n  }\n\n  /** Sets the start and end positions of the text selection (0-based). */\n  setSelectionRange(\n    selectionStart: number,\n    selectionEnd: number,\n    selectionDirection: 'forward' | 'backward' | 'none' = 'none'\n  ) {\n    this.input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);\n  }\n\n  /** Replaces a range of text with a new string. */\n  setRangeText(\n    replacement: string,\n    start?: number,\n    end?: number,\n    selectMode: 'select' | 'start' | 'end' | 'preserve' = 'preserve'\n  ) {\n    const selectionStart = start ?? this.input.selectionStart!;\n    const selectionEnd = end ?? this.input.selectionEnd!;\n\n    this.input.setRangeText(replacement, selectionStart, selectionEnd, selectMode);\n\n    if (this.value !== this.input.value) {\n      this.value = this.input.value;\n    }\n  }\n\n  /** Displays the browser picker for an input element (only works if the browser supports it for the input type). */\n  showPicker() {\n    if ('showPicker' in HTMLInputElement.prototype) {\n      this.input.showPicker();\n    }\n  }\n\n  /** Increments the value of a numeric input type by the value of the step attribute. */\n  stepUp() {\n    this.input.stepUp();\n    if (this.value !== this.input.value) {\n      this.value = this.input.value;\n    }\n  }\n\n  /** Decrements the value of a numeric input type by the value of the step attribute. */\n  stepDown() {\n    this.input.stepDown();\n    if (this.value !== this.input.value) {\n      this.value = this.input.value;\n    }\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.input.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const hasLabelSlot = this.hasSlotController.test('label');\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasLabel = this.label ? true : !!hasLabelSlot;\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n    const hasClearIcon = this.clearable && !this.disabled && !this.readonly;\n    const isClearIconVisible = hasClearIcon && (typeof this.value === 'number' || this.value.length > 0);\n\n    return html`\n      <div\n        part=\"form-control\"\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--has-label': hasLabel,\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          part=\"form-control-label\"\n          class=\"form-control__label\"\n          for=\"input\"\n          aria-hidden=${hasLabel ? 'false' : 'true'}\n        >\n          <slot name=\"label\">${this.label}</slot>\n        </label>\n\n        <div part=\"form-control-input\" class=\"form-control-input\">\n          <div\n            part=\"base\"\n            class=${classMap({\n              input: true,\n\n              // Sizes\n              'input--small': this.size === 'small',\n              'input--medium': this.size === 'medium',\n              'input--large': this.size === 'large',\n\n              // States\n              'input--pill': this.pill,\n              'input--standard': !this.filled,\n              'input--filled': this.filled,\n              'input--disabled': this.disabled,\n              'input--focused': this.hasFocus,\n              'input--empty': !this.value,\n              'input--no-spin-buttons': this.noSpinButtons\n            })}\n          >\n            <span part=\"prefix\" class=\"input__prefix\">\n              <slot name=\"prefix\"></slot>\n            </span>\n\n            <input\n              part=\"input\"\n              id=\"input\"\n              class=\"input__control\"\n              type=${this.type === 'password' && this.passwordVisible ? 'text' : this.type}\n              title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n              name=${ifDefined(this.name)}\n              ?disabled=${this.disabled}\n              ?readonly=${this.readonly}\n              ?required=${this.required}\n              placeholder=${ifDefined(this.placeholder)}\n              minlength=${ifDefined(this.minlength)}\n              maxlength=${ifDefined(this.maxlength)}\n              min=${ifDefined(this.min)}\n              max=${ifDefined(this.max)}\n              step=${ifDefined(this.step as number)}\n              .value=${live(this.value)}\n              autocapitalize=${ifDefined(this.autocapitalize)}\n              autocomplete=${ifDefined(this.autocomplete)}\n              autocorrect=${ifDefined(this.autocorrect)}\n              ?autofocus=${this.autofocus}\n              spellcheck=${this.spellcheck}\n              pattern=${ifDefined(this.pattern)}\n              enterkeyhint=${ifDefined(this.enterkeyhint)}\n              inputmode=${ifDefined(this.inputmode)}\n              aria-describedby=\"help-text\"\n              @change=${this.handleChange}\n              @input=${this.handleInput}\n              @invalid=${this.handleInvalid}\n              @keydown=${this.handleKeyDown}\n              @focus=${this.handleFocus}\n              @blur=${this.handleBlur}\n            />\n\n            ${isClearIconVisible\n              ? html`\n                  <button\n                    part=\"clear-button\"\n                    class=\"input__clear\"\n                    type=\"button\"\n                    aria-label=${this.localize.term('clearEntry')}\n                    @click=${this.handleClearClick}\n                    tabindex=\"-1\"\n                  >\n                    <slot name=\"clear-icon\">\n                      <sl-icon name=\"x-circle-fill\" library=\"system\"></sl-icon>\n                    </slot>\n                  </button>\n                `\n              : ''}\n            ${this.passwordToggle && !this.disabled\n              ? html`\n                  <button\n                    part=\"password-toggle-button\"\n                    class=\"input__password-toggle\"\n                    type=\"button\"\n                    aria-label=${this.localize.term(this.passwordVisible ? 'hidePassword' : 'showPassword')}\n                    @click=${this.handlePasswordToggle}\n                    tabindex=\"-1\"\n                  >\n                    ${this.passwordVisible\n                      ? html`\n                          <slot name=\"show-password-icon\">\n                            <sl-icon name=\"eye-slash\" library=\"system\"></sl-icon>\n                          </slot>\n                        `\n                      : html`\n                          <slot name=\"hide-password-icon\">\n                            <sl-icon name=\"eye\" library=\"system\"></sl-icon>\n                          </slot>\n                        `}\n                  </button>\n                `\n              : ''}\n\n            <span part=\"suffix\" class=\"input__suffix\">\n              <slot name=\"suffix\"></slot>\n            </span>\n          </div>\n        </div>\n\n        <div\n          part=\"form-control-help-text\"\n          id=\"help-text\"\n          class=\"form-control__help-text\"\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/input/input.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  .input {\n    flex: 1 1 auto;\n    display: inline-flex;\n    align-items: stretch;\n    justify-content: start;\n    position: relative;\n    width: 100%;\n    font-family: var(--sl-input-font-family);\n    font-weight: var(--sl-input-font-weight);\n    letter-spacing: var(--sl-input-letter-spacing);\n    vertical-align: middle;\n    overflow: hidden;\n    cursor: text;\n    transition:\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) border,\n      var(--sl-transition-fast) box-shadow,\n      var(--sl-transition-fast) background-color;\n  }\n\n  /* Standard inputs */\n  .input--standard {\n    background-color: var(--sl-input-background-color);\n    border: solid var(--sl-input-border-width) var(--sl-input-border-color);\n  }\n\n  .input--standard:hover:not(.input--disabled) {\n    background-color: var(--sl-input-background-color-hover);\n    border-color: var(--sl-input-border-color-hover);\n  }\n\n  .input--standard.input--focused:not(.input--disabled) {\n    background-color: var(--sl-input-background-color-focus);\n    border-color: var(--sl-input-border-color-focus);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);\n  }\n\n  .input--standard.input--focused:not(.input--disabled) .input__control {\n    color: var(--sl-input-color-focus);\n  }\n\n  .input--standard.input--disabled {\n    background-color: var(--sl-input-background-color-disabled);\n    border-color: var(--sl-input-border-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .input--standard.input--disabled .input__control {\n    color: var(--sl-input-color-disabled);\n  }\n\n  .input--standard.input--disabled .input__control::placeholder {\n    color: var(--sl-input-placeholder-color-disabled);\n  }\n\n  /* Filled inputs */\n  .input--filled {\n    border: none;\n    background-color: var(--sl-input-filled-background-color);\n    color: var(--sl-input-color);\n  }\n\n  .input--filled:hover:not(.input--disabled) {\n    background-color: var(--sl-input-filled-background-color-hover);\n  }\n\n  .input--filled.input--focused:not(.input--disabled) {\n    background-color: var(--sl-input-filled-background-color-focus);\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .input--filled.input--disabled {\n    background-color: var(--sl-input-filled-background-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .input__control {\n    flex: 1 1 auto;\n    font-family: inherit;\n    font-size: inherit;\n    font-weight: inherit;\n    min-width: 0;\n    height: 100%;\n    color: var(--sl-input-color);\n    border: none;\n    background: inherit;\n    box-shadow: none;\n    padding: 0;\n    margin: 0;\n    cursor: inherit;\n    -webkit-appearance: none;\n  }\n\n  .input__control::-webkit-search-decoration,\n  .input__control::-webkit-search-cancel-button,\n  .input__control::-webkit-search-results-button,\n  .input__control::-webkit-search-results-decoration {\n    -webkit-appearance: none;\n  }\n\n  .input__control:-webkit-autofill,\n  .input__control:-webkit-autofill:hover,\n  .input__control:-webkit-autofill:focus,\n  .input__control:-webkit-autofill:active {\n    box-shadow: 0 0 0 var(--sl-input-height-large) var(--sl-input-background-color-hover) inset !important;\n    -webkit-text-fill-color: var(--sl-color-primary-500);\n    caret-color: var(--sl-input-color);\n  }\n\n  .input--filled .input__control:-webkit-autofill,\n  .input--filled .input__control:-webkit-autofill:hover,\n  .input--filled .input__control:-webkit-autofill:focus,\n  .input--filled .input__control:-webkit-autofill:active {\n    box-shadow: 0 0 0 var(--sl-input-height-large) var(--sl-input-filled-background-color) inset !important;\n  }\n\n  .input__control::placeholder {\n    color: var(--sl-input-placeholder-color);\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  .input:hover:not(.input--disabled) .input__control {\n    color: var(--sl-input-color-hover);\n  }\n\n  .input__control:focus {\n    outline: none;\n  }\n\n  .input__prefix,\n  .input__suffix {\n    display: inline-flex;\n    flex: 0 0 auto;\n    align-items: center;\n    cursor: default;\n  }\n\n  .input__prefix ::slotted(sl-icon),\n  .input__suffix ::slotted(sl-icon) {\n    color: var(--sl-input-icon-color);\n  }\n\n  /*\n   * Size modifiers\n   */\n\n  .input--small {\n    border-radius: var(--sl-input-border-radius-small);\n    font-size: var(--sl-input-font-size-small);\n    height: var(--sl-input-height-small);\n  }\n\n  .input--small .input__control {\n    height: calc(var(--sl-input-height-small) - var(--sl-input-border-width) * 2);\n    padding: 0 var(--sl-input-spacing-small);\n  }\n\n  .input--small .input__clear,\n  .input--small .input__password-toggle {\n    width: calc(1em + var(--sl-input-spacing-small) * 2);\n  }\n\n  .input--small .input__prefix ::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-small);\n  }\n\n  .input--small .input__suffix ::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-small);\n  }\n\n  .input--medium {\n    border-radius: var(--sl-input-border-radius-medium);\n    font-size: var(--sl-input-font-size-medium);\n    height: var(--sl-input-height-medium);\n  }\n\n  .input--medium .input__control {\n    height: calc(var(--sl-input-height-medium) - var(--sl-input-border-width) * 2);\n    padding: 0 var(--sl-input-spacing-medium);\n  }\n\n  .input--medium .input__clear,\n  .input--medium .input__password-toggle {\n    width: calc(1em + var(--sl-input-spacing-medium) * 2);\n  }\n\n  .input--medium .input__prefix ::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-medium);\n  }\n\n  .input--medium .input__suffix ::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-medium);\n  }\n\n  .input--large {\n    border-radius: var(--sl-input-border-radius-large);\n    font-size: var(--sl-input-font-size-large);\n    height: var(--sl-input-height-large);\n  }\n\n  .input--large .input__control {\n    height: calc(var(--sl-input-height-large) - var(--sl-input-border-width) * 2);\n    padding: 0 var(--sl-input-spacing-large);\n  }\n\n  .input--large .input__clear,\n  .input--large .input__password-toggle {\n    width: calc(1em + var(--sl-input-spacing-large) * 2);\n  }\n\n  .input--large .input__prefix ::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-large);\n  }\n\n  .input--large .input__suffix ::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-large);\n  }\n\n  /*\n   * Pill modifier\n   */\n\n  .input--pill.input--small {\n    border-radius: var(--sl-input-height-small);\n  }\n\n  .input--pill.input--medium {\n    border-radius: var(--sl-input-height-medium);\n  }\n\n  .input--pill.input--large {\n    border-radius: var(--sl-input-height-large);\n  }\n\n  /*\n   * Clearable + Password Toggle\n   */\n\n  .input__clear,\n  .input__password-toggle {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    font-size: inherit;\n    color: var(--sl-input-icon-color);\n    border: none;\n    background: none;\n    padding: 0;\n    transition: var(--sl-transition-fast) color;\n    cursor: pointer;\n  }\n\n  .input__clear:hover,\n  .input__password-toggle:hover {\n    color: var(--sl-input-icon-color-hover);\n  }\n\n  .input__clear:focus,\n  .input__password-toggle:focus {\n    outline: none;\n  }\n\n  /* Don't show the browser's password toggle in Edge */\n  ::-ms-reveal {\n    display: none;\n  }\n\n  /* Hide the built-in number spinner */\n  .input--no-spin-buttons input[type='number']::-webkit-outer-spin-button,\n  .input--no-spin-buttons input[type='number']::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    display: none;\n  }\n\n  .input--no-spin-buttons input[type='number'] {\n    -moz-appearance: textfield;\n  }\n`;\n"
  },
  {
    "path": "src/components/input/input.test.ts",
    "content": "// eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment\nimport { elementUpdated, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { getFormControls, serialize } from '../../../dist/shoelace.js';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands'; // must come from the same module\nimport sinon from 'sinon';\nimport type SlInput from './input.js';\n\ndescribe('<sl-input>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlInput>(html` <sl-input label=\"Name\"></sl-input> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlInput>(html` <sl-input></sl-input> `);\n\n    expect(el.type).to.equal('text');\n    expect(el.size).to.equal('medium');\n    expect(el.name).to.equal('');\n    expect(el.value).to.equal('');\n    expect(el.defaultValue).to.equal('');\n    expect(el.title).to.equal('');\n    expect(el.filled).to.be.false;\n    expect(el.pill).to.be.false;\n    expect(el.label).to.equal('');\n    expect(el.helpText).to.equal('');\n    expect(el.clearable).to.be.false;\n    expect(el.passwordToggle).to.be.false;\n    expect(el.passwordVisible).to.be.false;\n    expect(el.noSpinButtons).to.be.false;\n    expect(el.placeholder).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.readonly).to.be.false;\n    expect(el.minlength).to.be.undefined;\n    expect(el.maxlength).to.be.undefined;\n    expect(el.min).to.be.undefined;\n    expect(el.max).to.be.undefined;\n    expect(el.step).to.be.undefined;\n    expect(el.pattern).to.be.undefined;\n    expect(el.required).to.be.false;\n    expect(el.autocapitalize).to.be.undefined;\n    expect(el.autocorrect).to.be.undefined;\n    expect(el.autocomplete).to.be.undefined;\n    expect(el.autofocus).to.be.undefined;\n    expect(el.enterkeyhint).to.be.undefined;\n    expect(el.spellcheck).to.be.true;\n    expect(el.inputmode).to.be.undefined;\n    expect(el.valueAsDate).to.be.null;\n    expect(isNaN(el.valueAsNumber)).to.be.true;\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlInput>(html` <sl-input title=\"Test\"></sl-input> `);\n    const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~=\"input\"]')!;\n\n    expect(input.title).to.equal('Test');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlInput>(html` <sl-input disabled></sl-input> `);\n    const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~=\"input\"]')!;\n\n    expect(input.disabled).to.be.true;\n  });\n\n  describe('value methods', () => {\n    it('should set the value as a date when using valueAsDate', async () => {\n      const el = document.createElement(`sl-input`);\n      el.type = 'date';\n      const today = new Date();\n\n      el.valueAsDate = today;\n\n      // Test before we render in the dom\n      expect(el.value).to.equal(today.toISOString().split('T')[0]);\n      expect(el.valueAsDate.toISOString().split('T')[0]).to.equal(today.toISOString().split('T')[0]);\n\n      document.body.appendChild(el);\n\n      await elementUpdated(el);\n\n      // Update valueAsDate after we render to make sure it reflects properly\n      el.valueAsDate = null;\n\n      await elementUpdated(el);\n\n      expect(el.value).to.equal('');\n      expect(el.valueAsDate).to.equal(null);\n\n      // Update again with a real date to make sure it works\n      el.valueAsDate = today;\n\n      await elementUpdated(el);\n\n      expect(el.value).to.equal(today.toISOString().split('T')[0]);\n      expect(el.valueAsDate.toISOString().split('T')[0]).to.equal(today.toISOString().split('T')[0]);\n\n      el.remove();\n    });\n\n    it('should set the value as a number when using valueAsNumber', async () => {\n      const el = document.createElement(`sl-input`);\n      el.type = 'number';\n      const num = 12345;\n\n      el.valueAsNumber = num;\n\n      expect(el.value).to.equal(num.toString());\n      expect(el.valueAsNumber).to.equal(num);\n\n      document.body.appendChild(el);\n\n      await elementUpdated(el);\n\n      // Wait for render, then update the value\n      const otherNum = 4567;\n      el.valueAsNumber = otherNum;\n\n      await elementUpdated(el);\n\n      expect(el.value).to.equal(otherNum.toString());\n      expect(el.valueAsNumber).to.equal(otherNum);\n\n      // Re-set valueAsNumber and make sure it updates.\n      el.valueAsNumber = num;\n      await elementUpdated(el);\n      expect(el.value).to.equal(num.toString());\n      expect(el.valueAsNumber).to.equal(num);\n\n      el.remove();\n    });\n  });\n\n  it('should focus the input when clicking on the label', async () => {\n    const el = await fixture<SlInput>(html` <sl-input label=\"Name\"></sl-input> `);\n    const label = el.shadowRoot!.querySelector('[part~=\"form-control-label\"]')!;\n    const focusHandler = sinon.spy();\n\n    el.addEventListener('sl-focus', focusHandler);\n    (label as HTMLLabelElement).click();\n    await waitUntil(() => focusHandler.calledOnce);\n\n    expect(focusHandler).to.have.been.calledOnce;\n  });\n\n  describe('when using constraint validation', () => {\n    it('should be valid by default', async () => {\n      const el = await fixture<SlInput>(html` <sl-input></sl-input> `);\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it('should be invalid when required and empty', async () => {\n      const el = await fixture<SlInput>(html` <sl-input required></sl-input> `);\n      expect(el.reportValidity()).to.be.false;\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should be invalid when required and disabled is removed', async () => {\n      const el = await fixture<SlInput>(html` <sl-input disabled required></sl-input> `);\n      el.disabled = false;\n      await el.updateComplete;\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when valid', async () => {\n      const el = await fixture<SlInput>(html` <sl-input required value=\"a\"></sl-input> `);\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.false;\n      expect(el.hasAttribute('data-valid')).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      el.focus();\n      await el.updateComplete;\n      await sendKeys({ press: 'b' });\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.true;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when invalid', async () => {\n      const el = await fixture<SlInput>(html` <sl-input required></sl-input> `);\n\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.true;\n      expect(el.hasAttribute('data-valid')).to.be.false;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      el.focus();\n      await el.updateComplete;\n      await sendKeys({ press: 'a' });\n      await sendKeys({ press: 'Backspace' });\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.hasAttribute('data-user-invalid')).to.be.true;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-input required></sl-input></form> `);\n      const input = el.querySelector<SlInput>('sl-input')!;\n\n      expect(input.hasAttribute('data-required')).to.be.true;\n      expect(input.hasAttribute('data-optional')).to.be.false;\n      expect(input.hasAttribute('data-invalid')).to.be.true;\n      expect(input.hasAttribute('data-valid')).to.be.false;\n      expect(input.hasAttribute('data-user-invalid')).to.be.false;\n      expect(input.hasAttribute('data-user-valid')).to.be.false;\n    });\n  });\n\n  describe('when submitting a form', () => {\n    it('should serialize its name and value with FormData', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-input name=\"a\" value=\"1\"></sl-input></form> `);\n      const formData = new FormData(form);\n      expect(formData.get('a')).to.equal('1');\n    });\n\n    it('should serialize its name and value with JSON', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-input name=\"a\" value=\"1\"></sl-input></form> `);\n      const json = serialize(form) as { a: '1' };\n      expect(json.a).to.equal('1');\n    });\n\n    it('should submit the form when pressing enter in a form without a submit button', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-input></sl-input></form> `);\n      const input = form.querySelector('sl-input')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => event.preventDefault());\n\n      form.addEventListener('submit', submitHandler);\n      input.focus();\n      await sendKeys({ press: 'Enter' });\n      await waitUntil(() => submitHandler.calledOnce);\n\n      expect(submitHandler).to.have.been.calledOnce;\n    });\n\n    it('should prevent submission when pressing enter in an input and canceling the keydown event', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-input></sl-input></form> `);\n      const input = form.querySelector('sl-input')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => event.preventDefault());\n      const keydownHandler = sinon.spy((event: KeyboardEvent) => {\n        if (event.key === 'Enter') {\n          event.preventDefault();\n        }\n      });\n\n      form.addEventListener('submit', submitHandler);\n      input.addEventListener('keydown', keydownHandler);\n      input.focus();\n      await sendKeys({ press: 'Enter' });\n      await waitUntil(() => keydownHandler.calledOnce);\n\n      expect(keydownHandler).to.have.been.calledOnce;\n      expect(submitHandler).to.not.have.been.called;\n    });\n\n    it('should be invalid when setCustomValidity() is called with a non-empty value', async () => {\n      const input = await fixture<HTMLFormElement>(html` <sl-input></sl-input> `);\n\n      input.setCustomValidity('Invalid selection');\n      await input.updateComplete;\n\n      expect(input.checkValidity()).to.be.false;\n      expect(input.hasAttribute('data-invalid')).to.be.true;\n      expect(input.hasAttribute('data-valid')).to.be.false;\n      expect(input.hasAttribute('data-user-invalid')).to.be.false;\n      expect(input.hasAttribute('data-user-valid')).to.be.false;\n\n      input.focus();\n      await sendKeys({ type: 'test' });\n      await input.updateComplete;\n      input.blur();\n      await input.updateComplete;\n\n      expect(input.hasAttribute('data-user-invalid')).to.be.true;\n      expect(input.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-input form=\"f\" name=\"a\" value=\"1\"></sl-input>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('1');\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-input name=\"a\" value=\"test\"></sl-input>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const input = form.querySelector('sl-input')!;\n      input.value = '1234';\n\n      await input.updateComplete;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await input.updateComplete;\n\n      expect(input.value).to.equal('test');\n\n      input.defaultValue = '';\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await input.updateComplete;\n\n      expect(input.value).to.equal('');\n    });\n  });\n\n  describe('when calling HTMLFormElement.reportValidity()', () => {\n    it('should be invalid when the input is empty and form.reportValidity() is called', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-input required value=\"\"></sl-input>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n\n      expect(form.reportValidity()).to.be.false;\n    });\n\n    it('should be valid when the input is empty, reportValidity() is called, and the form has novalidate', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form novalidate>\n          <sl-input required value=\"\"></sl-input>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n\n      expect(form.reportValidity()).to.be.true;\n    });\n\n    it('should be invalid when a native input is empty and form.reportValidity() is called', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <input required value=\"\"></input>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n\n      expect(form.reportValidity()).to.be.false;\n    });\n  });\n\n  describe('when the value changes', () => {\n    it('should emit sl-change and sl-input when the user types in the input', async () => {\n      const el = await fixture<SlInput>(html` <sl-input></sl-input> `);\n      const inputHandler = sinon.spy();\n      const changeHandler = sinon.spy();\n\n      el.addEventListener('sl-input', inputHandler);\n      el.addEventListener('sl-change', changeHandler);\n      el.focus();\n      await sendKeys({ type: 'abc' });\n      el.blur();\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledThrice;\n    });\n\n    it('should not emit sl-change or sl-input when the value is set programmatically', async () => {\n      const el = await fixture<SlInput>(html` <sl-input></sl-input> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.value = 'abc';\n\n      await el.updateComplete;\n    });\n\n    it('should not emit sl-change or sl-input when calling setRangeText()', async () => {\n      const el = await fixture<SlInput>(html` <sl-input value=\"hi there\"></sl-input> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.focus();\n      el.setSelectionRange(0, 2);\n      el.setRangeText('hello');\n\n      await el.updateComplete;\n    });\n  });\n\n  describe('when type=\"number\"', () => {\n    it('should be valid when the value is within the boundary of a step', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\".5\" value=\"1.5\"></sl-input> `);\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it('should be invalid when the value is not within the boundary of a step', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\".5\" value=\"1.25\"></sl-input> `);\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should update validity when step changes', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\".5\" value=\"1.5\"></sl-input> `);\n      expect(el.checkValidity()).to.be.true;\n\n      el.step = 1;\n      await el.updateComplete;\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should increment by step when stepUp() is called', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\"2\" value=\"2\"></sl-input> `);\n\n      el.stepUp();\n      await el.updateComplete;\n      expect(el.value).to.equal('4');\n    });\n\n    it('should decrement by step when stepDown() is called', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\"2\" value=\"2\"></sl-input> `);\n\n      el.stepDown();\n      await el.updateComplete;\n      expect(el.value).to.equal('0');\n    });\n\n    it('should not emit sl-input or sl-change when stepUp() is called programmatically', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\"2\" value=\"2\"></sl-input> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.stepUp();\n\n      await el.updateComplete;\n    });\n\n    it('should not emit sl-input and sl-change when stepDown() is called programmatically', async () => {\n      const el = await fixture<SlInput>(html` <sl-input type=\"number\" step=\"2\" value=\"2\"></sl-input> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.stepDown();\n\n      await el.updateComplete;\n    });\n  });\n\n  describe('when using spellcheck', () => {\n    it('should enable spellcheck when no attribute is present', async () => {\n      const el = await fixture<SlInput>(html` <sl-input></sl-input> `);\n      const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;\n      expect(input.getAttribute('spellcheck')).to.equal('true');\n      expect(input.spellcheck).to.be.true;\n    });\n\n    it('should enable spellcheck when set to \"true\"', async () => {\n      const el = await fixture<SlInput>(html` <sl-input spellcheck=\"true\"></sl-input> `);\n      const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;\n      expect(input.getAttribute('spellcheck')).to.equal('true');\n      expect(input.spellcheck).to.be.true;\n    });\n\n    it('should disable spellcheck when set to \"false\"', async () => {\n      const el = await fixture<SlInput>(html` <sl-input spellcheck=\"false\"></sl-input> `);\n      const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;\n      expect(input.getAttribute('spellcheck')).to.equal('false');\n      expect(input.spellcheck).to.be.false;\n    });\n  });\n\n  describe('when using FormControlController', () => {\n    it('should submit with the correct form when the form attribute changes', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f1\">\n            <input type=\"hidden\" name=\"b\" value=\"2\" />\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <form id=\"f2\">\n            <input type=\"hidden\" name=\"c\" value=\"3\" />\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-input form=\"f1\" name=\"a\" value=\"1\"></sl-input>\n        </div>\n      `);\n      const form = el.querySelector<HTMLFormElement>('#f2')!;\n      const input = document.querySelector('sl-input')!;\n\n      input.form = 'f2';\n      await input.updateComplete;\n\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('1');\n      expect(formData.get('b')).to.be.null;\n      expect(formData.get('c')).to.equal('3');\n    });\n  });\n\n  describe('when using the getFormControls() function', () => {\n    it('should return both native and Shoelace form controls in the correct DOM order', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <input type=\"text\" name=\"a\" value=\"1\" form=\"f1\" />\n          <sl-input type=\"text\" name=\"b\" value=\"2\" form=\"f1\"></sl-input>\n          <form id=\"f1\">\n            <input type=\"hidden\" name=\"c\" value=\"3\" />\n            <input type=\"text\" name=\"d\" value=\"4\" />\n            <sl-input name=\"e\" value=\"5\"></sl-input>\n            <textarea name=\"f\">6</textarea>\n            <sl-textarea name=\"g\" value=\"7\"></sl-textarea>\n            <sl-checkbox name=\"h\" value=\"8\"></sl-checkbox>\n          </form>\n          <input type=\"text\" name=\"i\" value=\"9\" form=\"f1\" />\n          <sl-input type=\"text\" name=\"j\" value=\"10\" form=\"f1\"></sl-input>\n        </div>\n      `);\n      const form = el.querySelector<HTMLFormElement>('form')!;\n\n      const formControls = getFormControls(form); // eslint-disable-line\n      expect(formControls.length).to.equal(10); // eslint-disable-line\n      expect(formControls.map((fc: HTMLInputElement) => fc.value).join('')).to.equal('12345678910'); // eslint-disable-line\n    });\n  });\n\n  describe('when using the setRangeText() function', () => {\n    it('should set replacement text in the correct location', async () => {\n      const el = await fixture<SlInput>(html` <sl-input value=\"test\"></sl-input> `);\n\n      el.focus();\n      el.setSelectionRange(1, 3);\n      el.setRangeText('boom');\n      await el.updateComplete;\n      expect(el.value).to.equal('tboomt'); // cspell:disable-line\n    });\n  });\n\n  runFormControlBaseTests('sl-input');\n});\n"
  },
  {
    "path": "src/components/input/input.ts",
    "content": "import SlInput from './input.component.js';\n\nexport * from './input.component.js';\nexport default SlInput;\n\nSlInput.define('sl-input');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-input': SlInput;\n  }\n}\n"
  },
  {
    "path": "src/components/menu/menu.component.ts",
    "content": "import { html } from 'lit';\nimport { query } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './menu.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type SlMenuItem from '../menu-item/menu-item.component.js';\n\nexport interface MenuSelectEventDetail {\n  item: SlMenuItem;\n}\n\n/**\n * @summary Menus provide a list of options for the user to choose from.\n * @documentation https://shoelace.style/components/menu\n * @status stable\n * @since 2.0\n *\n * @slot - The menu's content, including menu items, menu labels, and dividers.\n *\n * @event {{ item: SlMenuItem }} sl-select - Emitted when a menu item is selected.\n */\nexport default class SlMenu extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  @query('slot') defaultSlot: HTMLSlotElement;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setAttribute('role', 'menu');\n  }\n\n  private handleClick(event: MouseEvent) {\n    const menuItemTypes = ['menuitem', 'menuitemcheckbox'];\n\n    const composedPath = event.composedPath();\n    const target = composedPath.find((el: Element) => menuItemTypes.includes(el?.getAttribute?.('role') || ''));\n\n    if (!target) return;\n\n    const closestMenu = composedPath.find((el: Element) => el?.getAttribute?.('role') === 'menu');\n    const clickHasSubmenu = closestMenu !== this;\n\n    // Make sure we're the menu thats supposed to be handling the click event.\n    if (clickHasSubmenu) return;\n\n    // This isn't true. But we use it for TypeScript checks below.\n    const item = target as SlMenuItem;\n\n    if (item.type === 'checkbox') {\n      item.checked = !item.checked;\n    }\n\n    this.emit('sl-select', { detail: { item } });\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    // Make a selection when pressing enter or space\n    if (event.key === 'Enter' || event.key === ' ') {\n      const item = this.getCurrentItem();\n      event.preventDefault();\n      event.stopPropagation();\n\n      // Simulate a click to support @click handlers on menu items that also work with the keyboard\n      item?.click();\n    }\n\n    // Move the selection when pressing down or up\n    else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {\n      const items = this.getAllItems();\n      const activeItem = this.getCurrentItem();\n      let index = activeItem ? items.indexOf(activeItem) : 0;\n\n      if (items.length > 0) {\n        event.preventDefault();\n        event.stopPropagation();\n\n        if (event.key === 'ArrowDown') {\n          index++;\n        } else if (event.key === 'ArrowUp') {\n          index--;\n        } else if (event.key === 'Home') {\n          index = 0;\n        } else if (event.key === 'End') {\n          index = items.length - 1;\n        }\n\n        if (index < 0) {\n          index = items.length - 1;\n        }\n        if (index > items.length - 1) {\n          index = 0;\n        }\n\n        this.setCurrentItem(items[index]);\n        items[index].focus();\n      }\n    }\n  }\n\n  private handleMouseDown(event: MouseEvent) {\n    const target = event.target as HTMLElement;\n\n    if (this.isMenuItem(target)) {\n      this.setCurrentItem(target as SlMenuItem);\n    }\n  }\n\n  private handleSlotChange() {\n    const items = this.getAllItems();\n\n    // Reset the roving tab index when the slotted items change\n    if (items.length > 0) {\n      this.setCurrentItem(items[0]);\n    }\n  }\n\n  private isMenuItem(item: HTMLElement) {\n    return (\n      item.tagName.toLowerCase() === 'sl-menu-item' ||\n      ['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(item.getAttribute('role') ?? '')\n    );\n  }\n\n  /** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */\n  getAllItems() {\n    return [...this.defaultSlot.assignedElements({ flatten: true })].filter((el: HTMLElement) => {\n      if (el.inert || !this.isMenuItem(el)) {\n        return false;\n      }\n      return true;\n    }) as SlMenuItem[];\n  }\n\n  /**\n   * @internal Gets the current menu item, which is the menu item that has `tabindex=\"0\"` within the roving tab index.\n   * The menu item may or may not have focus, but for keyboard interaction purposes it's considered the \"active\" item.\n   */\n  getCurrentItem() {\n    return this.getAllItems().find(i => i.getAttribute('tabindex') === '0');\n  }\n\n  /**\n   * @internal Sets the current menu item to the specified element. This sets `tabindex=\"0\"` on the target element and\n   * `tabindex=\"-1\"` to all other items. This method must be called prior to setting focus on a menu item.\n   */\n  setCurrentItem(item: SlMenuItem) {\n    const items = this.getAllItems();\n\n    // Update tab indexes\n    items.forEach(i => {\n      i.setAttribute('tabindex', i === item ? '0' : '-1');\n    });\n  }\n\n  render() {\n    return html`\n      <slot\n        @slotchange=${this.handleSlotChange}\n        @click=${this.handleClick}\n        @keydown=${this.handleKeyDown}\n        @mousedown=${this.handleMouseDown}\n      ></slot>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/menu/menu.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n    position: relative;\n    background: var(--sl-panel-background-color);\n    border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);\n    border-radius: var(--sl-border-radius-medium);\n    padding: var(--sl-spacing-x-small) 0;\n    overflow: auto;\n    overscroll-behavior: none;\n  }\n\n  ::slotted(sl-divider) {\n    --spacing: var(--sl-spacing-x-small);\n  }\n`;\n"
  },
  {
    "path": "src/components/menu/menu.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { clickOnElement } from '../../internal/test.js';\nimport { expect, fixture } from '@open-wc/testing';\nimport { html } from 'lit';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type { SlSelectEvent } from '../../events/sl-select.js';\nimport type SlMenu from './menu.js';\nimport type SlMenuItem from '../menu-item/menu-item.component.js';\n\ndescribe('<sl-menu>', () => {\n  it('emits sl-select with the correct event detail when clicking an item', async () => {\n    const menu = await fixture<SlMenu>(html`\n      <sl-menu>\n        <sl-menu-item value=\"item-1\">Item 1</sl-menu-item>\n        <sl-menu-item value=\"item-2\">Item 2</sl-menu-item>\n        <sl-menu-item value=\"item-3\">Item 3</sl-menu-item>\n        <sl-menu-item value=\"item-4\">Item 4</sl-menu-item>\n      </sl-menu>\n    `);\n    const item2 = menu.querySelectorAll('sl-menu-item')[1];\n    const selectHandler = sinon.spy((event: SlSelectEvent) => {\n      const item = event.detail.item;\n      if (item !== item2) {\n        expect.fail('Incorrect event detail emitted with sl-select');\n      }\n    });\n\n    menu.addEventListener('sl-select', selectHandler);\n    await clickOnElement(item2);\n\n    expect(selectHandler).to.have.been.calledOnce;\n  });\n\n  it('can be selected via keyboard', async () => {\n    const menu = await fixture<SlMenu>(html`\n      <sl-menu>\n        <sl-menu-item value=\"item-1\">Item 1</sl-menu-item>\n        <sl-menu-item value=\"item-2\">Item 2</sl-menu-item>\n        <sl-menu-item value=\"item-3\">Item 3</sl-menu-item>\n        <sl-menu-item value=\"item-4\">Item 4</sl-menu-item>\n      </sl-menu>\n    `);\n    const [item1, item2] = menu.querySelectorAll('sl-menu-item');\n    const selectHandler = sinon.spy((event: SlSelectEvent) => {\n      const item = event.detail.item;\n      if (item !== item2) {\n        expect.fail('Incorrect item selected');\n      }\n    });\n\n    menu.addEventListener('sl-select', selectHandler);\n\n    item1.focus();\n    await item1.updateComplete;\n    await sendKeys({ press: 'ArrowDown' });\n    await sendKeys({ press: 'Enter' });\n\n    expect(selectHandler).to.have.been.calledOnce;\n  });\n\n  it('does not select disabled items when clicking', async () => {\n    const menu = await fixture<SlMenu>(html`\n      <sl-menu>\n        <sl-menu-item value=\"item-1\">Item 1</sl-menu-item>\n        <sl-menu-item value=\"item-2\" disabled>Item 2</sl-menu-item>\n        <sl-menu-item value=\"item-3\">Item 3</sl-menu-item>\n        <sl-menu-item value=\"item-4\">Item 4</sl-menu-item>\n      </sl-menu>\n    `);\n    const item2 = menu.querySelectorAll('sl-menu-item')[1];\n    const selectHandler = sinon.spy();\n\n    menu.addEventListener('sl-select', selectHandler);\n\n    await clickOnElement(item2);\n\n    expect(selectHandler).to.not.have.been.calledOnce;\n  });\n\n  it('does not select disabled items when pressing enter', async () => {\n    const menu = await fixture<SlMenu>(html`\n      <sl-menu>\n        <sl-menu-item value=\"item-1\">Item 1</sl-menu-item>\n        <sl-menu-item value=\"item-2\" disabled>Item 2</sl-menu-item>\n        <sl-menu-item value=\"item-3\">Item 3</sl-menu-item>\n        <sl-menu-item value=\"item-4\">Item 4</sl-menu-item>\n      </sl-menu>\n    `);\n    const [item1, item2] = menu.querySelectorAll('sl-menu-item');\n    const selectHandler = sinon.spy();\n\n    menu.addEventListener('sl-select', selectHandler);\n\n    item1.focus();\n    await item1.updateComplete;\n    await sendKeys({ press: 'ArrowDown' });\n    expect(document.activeElement).to.equal(item2);\n    await sendKeys({ press: 'Enter' });\n    await item2.updateComplete;\n\n    expect(selectHandler).to.not.have.been.called;\n  });\n\n  // @see https://github.com/shoelace-style/shoelace/issues/1596\n  it('Should fire \"sl-select\" when clicking an element within a menu-item', async () => {\n    // eslint-disable-next-line\n    const selectHandler = sinon.spy(() => {});\n\n    const menu: SlMenu = await fixture(html`\n      <sl-menu>\n        <sl-menu-item>\n          <span>Menu item</span>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    menu.addEventListener('sl-select', selectHandler);\n    const span = menu.querySelector('span')!;\n    await clickOnElement(span);\n\n    expect(selectHandler).to.have.been.calledOnce;\n  });\n\n  // @see https://github.com/shoelace-style/shoelace/issues/2115\n  it('Should be able to check a checkbox menu item in a submenu', async () => {\n    const menu: SlMenu = await fixture(html`\n      <sl-menu style=\"max-width: 200px;\">\n        <sl-menu-item>\n          <span>Menu item</span>\n          <sl-menu slot=\"submenu\">\n            <sl-menu-item type=\"checkbox\" checked>Checkbox</sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    const menuItem = menu.querySelector<SlMenuItem>('sl-menu-item')!;\n    const checkbox = menu.querySelector<SlMenuItem>(\"[type='checkbox']\")!;\n\n    expect(checkbox.checked).to.equal(true);\n    await clickOnElement(menuItem); // Focus the menu item\n    await sendKeys({ press: 'ArrowRight' }); // Open the submenu\n    await clickOnElement(checkbox); // Click the checkbox\n    await checkbox.updateComplete;\n    expect(checkbox.checked).to.equal(false);\n  });\n});\n"
  },
  {
    "path": "src/components/menu/menu.ts",
    "content": "import SlMenu from './menu.component.js';\n\nexport * from './menu.component.js';\nexport default SlMenu;\n\nSlMenu.define('sl-menu');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-menu': SlMenu;\n  }\n}\n"
  },
  {
    "path": "src/components/menu-item/menu-item.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { getTextContent, HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { SubmenuController } from './submenu-controller.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlPopup from '../popup/popup.component.js';\nimport SlSpinner from '../spinner/spinner.component.js';\nimport styles from './menu-item.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Menu items provide options for the user to pick from in a menu.\n * @documentation https://shoelace.style/components/menu-item\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n * @dependency sl-popup\n * @dependency sl-spinner\n *\n * @slot - The menu item's label.\n * @slot prefix - Used to prepend an icon or similar element to the menu item.\n * @slot suffix - Used to append an icon or similar element to the menu item.\n * @slot submenu - Used to denote a nested menu.\n *\n * @csspart base - The component's base wrapper.\n * @csspart checked-icon - The checked icon, which is only visible when the menu item is checked.\n * @csspart prefix - The prefix container.\n * @csspart label - The menu item label.\n * @csspart suffix - The suffix container.\n * @csspart spinner - The spinner that shows when the menu item is in the loading state.\n * @csspart spinner__base - The spinner's base part.\n * @csspart submenu-icon - The submenu icon, visible only when the menu item has a submenu (not yet implemented).\n *\n * @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu.\n */\nexport default class SlMenuItem extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-icon': SlIcon,\n    'sl-popup': SlPopup,\n    'sl-spinner': SlSpinner\n  };\n\n  private cachedTextLabel: string;\n  private readonly localize = new LocalizeController(this);\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n  @query('.menu-item') menuItem: HTMLElement;\n\n  /** The type of menu item to render. To use `checked`, this value must be set to `checkbox`. */\n  @property() type: 'normal' | 'checkbox' = 'normal';\n\n  /** Draws the item in a checked state. */\n  @property({ type: Boolean, reflect: true }) checked = false;\n\n  /** A unique value to store in the menu item. This can be used as a way to identify menu items when selected. */\n  @property() value = '';\n\n  /** Draws the menu item in a loading state. */\n  @property({ type: Boolean, reflect: true }) loading = false;\n\n  /** Draws the menu item in a disabled state, preventing selection. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  private readonly hasSlotController = new HasSlotController(this, 'submenu');\n  private submenuController: SubmenuController = new SubmenuController(this, this.hasSlotController);\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.addEventListener('click', this.handleHostClick);\n    this.addEventListener('mouseover', this.handleMouseOver);\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.removeEventListener('click', this.handleHostClick);\n    this.removeEventListener('mouseover', this.handleMouseOver);\n  }\n\n  private handleDefaultSlotChange() {\n    const textLabel = this.getTextLabel();\n\n    // Ignore the first time the label is set\n    if (typeof this.cachedTextLabel === 'undefined') {\n      this.cachedTextLabel = textLabel;\n      return;\n    }\n\n    // When the label changes, emit a slotchange event so parent controls see it\n    if (textLabel !== this.cachedTextLabel) {\n      this.cachedTextLabel = textLabel;\n      this.emit('slotchange', { bubbles: true, composed: false, cancelable: false });\n    }\n  }\n\n  private handleHostClick = (event: MouseEvent) => {\n    // Prevent the click event from being emitted when the button is disabled or loading\n    if (this.disabled) {\n      event.preventDefault();\n      event.stopImmediatePropagation();\n    }\n  };\n\n  private handleMouseOver = (event: MouseEvent) => {\n    this.focus();\n    event.stopPropagation();\n  };\n\n  @watch('checked')\n  handleCheckedChange() {\n    // For proper accessibility, users have to use type=\"checkbox\" to use the checked attribute\n    if (this.checked && this.type !== 'checkbox') {\n      this.checked = false;\n      console.error('The checked attribute can only be used on menu items with type=\"checkbox\"', this);\n      return;\n    }\n\n    // Only checkbox types can receive the aria-checked attribute\n    if (this.type === 'checkbox') {\n      this.setAttribute('aria-checked', this.checked ? 'true' : 'false');\n    } else {\n      this.removeAttribute('aria-checked');\n    }\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  @watch('type')\n  handleTypeChange() {\n    if (this.type === 'checkbox') {\n      this.setAttribute('role', 'menuitemcheckbox');\n      this.setAttribute('aria-checked', this.checked ? 'true' : 'false');\n    } else {\n      this.setAttribute('role', 'menuitem');\n      this.removeAttribute('aria-checked');\n    }\n  }\n\n  /** Returns a text label based on the contents of the menu item's default slot. */\n  getTextLabel() {\n    return getTextContent(this.defaultSlot);\n  }\n\n  isSubmenu() {\n    return this.hasSlotController.test('submenu');\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n    const isSubmenuExpanded = this.submenuController.isExpanded();\n\n    return html`\n      <div\n        id=\"anchor\"\n        part=\"base\"\n        class=${classMap({\n          'menu-item': true,\n          'menu-item--rtl': isRtl,\n          'menu-item--checked': this.checked,\n          'menu-item--disabled': this.disabled,\n          'menu-item--loading': this.loading,\n          'menu-item--has-submenu': this.isSubmenu(),\n          'menu-item--submenu-expanded': isSubmenuExpanded\n        })}\n        ?aria-haspopup=\"${this.isSubmenu()}\"\n        ?aria-expanded=\"${isSubmenuExpanded ? true : false}\"\n      >\n        <span part=\"checked-icon\" class=\"menu-item__check\">\n          <sl-icon name=\"check\" library=\"system\" aria-hidden=\"true\"></sl-icon>\n        </span>\n\n        <slot name=\"prefix\" part=\"prefix\" class=\"menu-item__prefix\"></slot>\n\n        <slot part=\"label\" class=\"menu-item__label\" @slotchange=${this.handleDefaultSlotChange}></slot>\n\n        <slot name=\"suffix\" part=\"suffix\" class=\"menu-item__suffix\"></slot>\n\n        <span part=\"submenu-icon\" class=\"menu-item__chevron\">\n          <sl-icon name=${isRtl ? 'chevron-left' : 'chevron-right'} library=\"system\" aria-hidden=\"true\"></sl-icon>\n        </span>\n\n        ${this.submenuController.renderSubmenu()}\n        ${this.loading ? html` <sl-spinner part=\"spinner\" exportparts=\"base:spinner__base\"></sl-spinner> ` : ''}\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/menu-item/menu-item.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --submenu-offset: -2px;\n\n    display: block;\n  }\n\n  :host([inert]) {\n    display: none;\n  }\n\n  .menu-item {\n    position: relative;\n    display: flex;\n    align-items: stretch;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    line-height: var(--sl-line-height-normal);\n    letter-spacing: var(--sl-letter-spacing-normal);\n    color: var(--sl-color-neutral-700);\n    padding: var(--sl-spacing-2x-small) var(--sl-spacing-2x-small);\n    transition: var(--sl-transition-fast) fill;\n    user-select: none;\n    -webkit-user-select: none;\n    white-space: nowrap;\n    cursor: pointer;\n  }\n\n  .menu-item.menu-item--disabled {\n    outline: none;\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .menu-item.menu-item--loading {\n    outline: none;\n    cursor: wait;\n  }\n\n  .menu-item.menu-item--loading *:not(sl-spinner) {\n    opacity: 0.5;\n  }\n\n  .menu-item--loading sl-spinner {\n    --indicator-color: currentColor;\n    --track-width: 1px;\n    position: absolute;\n    font-size: 0.75em;\n    top: calc(50% - 0.5em);\n    left: 0.65rem;\n    opacity: 1;\n  }\n\n  .menu-item .menu-item__label {\n    flex: 1 1 auto;\n    display: inline-block;\n    text-overflow: ellipsis;\n    overflow: hidden;\n  }\n\n  .menu-item .menu-item__prefix {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n  }\n\n  .menu-item .menu-item__prefix::slotted(*) {\n    margin-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .menu-item .menu-item__suffix {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n  }\n\n  .menu-item .menu-item__suffix::slotted(*) {\n    margin-inline-start: var(--sl-spacing-x-small);\n  }\n\n  /* Safe triangle */\n  .menu-item--submenu-expanded::after {\n    content: '';\n    position: fixed;\n    z-index: calc(var(--sl-z-index-dropdown) - 1);\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    clip-path: polygon(\n      var(--safe-triangle-cursor-x, 0) var(--safe-triangle-cursor-y, 0),\n      var(--safe-triangle-submenu-start-x, 0) var(--safe-triangle-submenu-start-y, 0),\n      var(--safe-triangle-submenu-end-x, 0) var(--safe-triangle-submenu-end-y, 0)\n    );\n  }\n\n  :host(:focus-visible) {\n    outline: none;\n  }\n\n  :host(:hover:not([aria-disabled='true'], :focus-visible)) .menu-item,\n  .menu-item--submenu-expanded {\n    background-color: var(--sl-color-neutral-100);\n    color: var(--sl-color-neutral-1000);\n  }\n\n  :host(:focus-visible) .menu-item {\n    outline: none;\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n    opacity: 1;\n  }\n\n  .menu-item .menu-item__check,\n  .menu-item .menu-item__chevron {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 1.5em;\n    visibility: hidden;\n  }\n\n  .menu-item--checked .menu-item__check,\n  .menu-item--has-submenu .menu-item__chevron {\n    visibility: visible;\n  }\n\n  /* Add elevation and z-index to submenus */\n  sl-popup::part(popup) {\n    box-shadow: var(--sl-shadow-large);\n    z-index: var(--sl-z-index-dropdown);\n    margin-left: var(--submenu-offset);\n  }\n\n  .menu-item--rtl sl-popup::part(popup) {\n    margin-left: calc(-1 * var(--submenu-offset));\n  }\n\n  @media (forced-colors: active) {\n    :host(:hover:not([aria-disabled='true'])) .menu-item,\n    :host(:focus-visible) .menu-item {\n      outline: dashed 1px SelectedItem;\n      outline-offset: -1px;\n    }\n  }\n\n  ::slotted(sl-menu) {\n    max-width: var(--auto-size-available-width) !important;\n    max-height: var(--auto-size-available-height) !important;\n  }\n`;\n"
  },
  {
    "path": "src/components/menu-item/menu-item.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type { SlSelectEvent } from '../../events/sl-select.js';\nimport type SlMenuItem from './menu-item.js';\n\ndescribe('<sl-menu-item>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item>Item 1</sl-menu-item>\n        <sl-menu-item>Item 2</sl-menu-item>\n        <sl-menu-item>Item 3</sl-menu-item>\n        <sl-divider></sl-divider>\n        <sl-menu-item type=\"checkbox\" checked>Checked</sl-menu-item>\n        <sl-menu-item type=\"checkbox\">Unchecked</sl-menu-item>\n      </sl-menu>\n    `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('should pass accessibility tests when using a submenu', async () => {\n    const el = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item>\n          Submenu\n          <sl-menu slot=\"submenu\">\n            <sl-menu-item>Submenu Item 1</sl-menu-item>\n            <sl-menu-item>Submenu Item 2</sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('should have the correct default properties', async () => {\n    const el = await fixture<SlMenuItem>(html` <sl-menu-item>Test</sl-menu-item> `);\n\n    expect(el.value).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.loading).to.equal(false);\n    expect(el.getAttribute('aria-disabled')).to.equal('false');\n  });\n\n  it('should render the correct aria attributes when disabled', async () => {\n    const el = await fixture<SlMenuItem>(html` <sl-menu-item disabled>Test</sl-menu-item> `);\n    expect(el.getAttribute('aria-disabled')).to.equal('true');\n  });\n\n  describe('when loading', () => {\n    it('should have a spinner present', async () => {\n      const el = await fixture<SlMenuItem>(html` <sl-menu-item loading>Menu Item Label</sl-menu-item> `);\n      expect(el.shadowRoot!.querySelector('sl-spinner')).to.exist;\n    });\n  });\n\n  it('should return a text label when calling getTextLabel()', async () => {\n    const el = await fixture<SlMenuItem>(html` <sl-menu-item>Test</sl-menu-item> `);\n    expect(el.getTextLabel()).to.equal('Test');\n  });\n\n  it('should emit the slotchange event when the label changes', async () => {\n    const el = await fixture<SlMenuItem>(html` <sl-menu-item>Text</sl-menu-item> `);\n    const slotChangeHandler = sinon.spy();\n\n    el.addEventListener('slotchange', slotChangeHandler);\n    el.textContent = 'New Text';\n    await waitUntil(() => slotChangeHandler.calledOnce);\n\n    expect(slotChangeHandler).to.have.been.calledOnce;\n  });\n\n  it('should render a hidden menu item when the inert attribute is used', async () => {\n    const menu = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item inert>Item 1</sl-menu-item>\n        <sl-menu-item>Item 2</sl-menu-item>\n        <sl-menu-item>Item 3</sl-menu-item>\n      </sl-menu>\n    `);\n    const item1 = menu.querySelector('sl-menu-item')!;\n\n    expect(getComputedStyle(item1).display).to.equal('none');\n  });\n\n  it('should not render a sl-popup if the slot=\"submenu\" attribute is missing, but the slot should exist in the component and be hidden.', async () => {\n    const menu = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item>\n          Item 1\n          <sl-menu>\n            <sl-menu-item> Nested Item 1 </sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    const menuItem: HTMLElement = menu.querySelector('sl-menu-item')!;\n    expect(menuItem.shadowRoot!.querySelector('sl-popup')).to.be.null;\n    const submenuSlot: HTMLElement = menuItem.shadowRoot!.querySelector('slot[name=\"submenu\"]')!;\n    expect(submenuSlot.hidden).to.be.true;\n  });\n\n  it('should render an sl-popup if the slot=\"submenu\" attribute is present', async () => {\n    const menu = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item id=\"test\">\n          Item 1\n          <sl-menu slot=\"submenu\">\n            <sl-menu-item> Nested Item 1 </sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    const menuItem = menu.querySelector('sl-menu-item')!;\n    expect(menuItem.shadowRoot!.querySelector('sl-popup')).to.be.not.null;\n    const submenuSlot: HTMLElement = menuItem.shadowRoot!.querySelector('slot[name=\"submenu\"]')!;\n    expect(submenuSlot.hidden).to.be.false;\n  });\n\n  it('should focus on first menuitem of submenu if ArrowRight is pressed on parent menuitem', async () => {\n    const menu = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item id=\"item-1\">\n          Submenu\n          <sl-menu slot=\"submenu\">\n            <sl-menu-item value=\"submenu-item-1\"> Nested Item 1 </sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    const selectHandler = sinon.spy((event: SlSelectEvent) => {\n      const item = event.detail.item;\n      expect(item.value).to.equal('submenu-item-1');\n    });\n    menu.addEventListener('sl-select', selectHandler);\n\n    const submenu = menu.querySelector('sl-menu-item');\n    submenu!.focus();\n    await menu.updateComplete;\n    await sendKeys({ press: 'ArrowRight' });\n    await menu.updateComplete;\n    await sendKeys({ press: 'Enter' });\n    await menu.updateComplete;\n    expect(selectHandler).to.have.been.calledOnce;\n  });\n\n  it('should focus on outer menu if ArrowRight is pressed on nested menuitem', async () => {\n    const menu = await fixture<SlMenuItem>(html`\n      <sl-menu>\n        <sl-menu-item value=\"outer-item-1\">\n          Submenu\n          <sl-menu slot=\"submenu\">\n            <sl-menu-item value=\"inner-item-1\"> Nested Item 1 </sl-menu-item>\n          </sl-menu>\n        </sl-menu-item>\n      </sl-menu>\n    `);\n\n    const focusHandler = sinon.spy((event: FocusEvent) => {\n      expect(event.target.value).to.equal('outer-item-1');\n      expect(event.relatedTarget.value).to.equal('inner-item-1');\n    });\n\n    const outerItem = menu.querySelector('sl-menu-item');\n    outerItem!.focus();\n    await menu.updateComplete;\n    await sendKeys({ press: 'ArrowRight' });\n\n    outerItem.addEventListener('focus', focusHandler);\n    await menu.updateComplete;\n    await sendKeys({ press: 'ArrowLeft' });\n    await menu.updateComplete;\n    expect(focusHandler).to.have.been.calledOnce;\n  });\n});\n"
  },
  {
    "path": "src/components/menu-item/menu-item.ts",
    "content": "import SlMenuItem from './menu-item.component.js';\n\nexport * from './menu-item.component.js';\nexport default SlMenuItem;\n\nSlMenuItem.define('sl-menu-item');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-menu-item': SlMenuItem;\n  }\n}\n"
  },
  {
    "path": "src/components/menu-item/submenu-controller.ts",
    "content": "import { createRef, ref, type Ref } from 'lit/directives/ref.js';\nimport { type HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport type SlMenuItem from './menu-item.js';\nimport type SlPopup from '../popup/popup.js';\n\n/** A reactive controller to manage the registration of event listeners for submenus. */\nexport class SubmenuController implements ReactiveController {\n  private host: ReactiveControllerHost & SlMenuItem;\n  private popupRef: Ref<SlPopup> = createRef();\n  private enableSubmenuTimer = -1;\n  private isConnected = false;\n  private isPopupConnected = false;\n  private skidding = 0;\n  private readonly hasSlotController: HasSlotController;\n  private readonly submenuOpenDelay = 100;\n\n  constructor(host: ReactiveControllerHost & SlMenuItem, hasSlotController: HasSlotController) {\n    (this.host = host).addController(this);\n    this.hasSlotController = hasSlotController;\n  }\n\n  hostConnected() {\n    if (this.hasSlotController.test('submenu') && !this.host.disabled) {\n      this.addListeners();\n    }\n  }\n\n  hostDisconnected() {\n    this.removeListeners();\n  }\n\n  hostUpdated() {\n    if (this.hasSlotController.test('submenu') && !this.host.disabled) {\n      this.addListeners();\n      this.updateSkidding();\n    } else {\n      this.removeListeners();\n    }\n  }\n\n  private addListeners() {\n    if (!this.isConnected) {\n      this.host.addEventListener('mousemove', this.handleMouseMove);\n      this.host.addEventListener('mouseover', this.handleMouseOver);\n      this.host.addEventListener('keydown', this.handleKeyDown);\n      this.host.addEventListener('click', this.handleClick);\n      this.host.addEventListener('focusout', this.handleFocusOut);\n      this.isConnected = true;\n    }\n\n    // The popup does not seem to get wired when the host is\n    // connected, so manage its listeners separately.\n    if (!this.isPopupConnected) {\n      if (this.popupRef.value) {\n        this.popupRef.value.addEventListener('mouseover', this.handlePopupMouseover);\n        this.popupRef.value.addEventListener('sl-reposition', this.handlePopupReposition);\n        this.isPopupConnected = true;\n      }\n    }\n  }\n\n  private removeListeners() {\n    if (this.isConnected) {\n      this.host.removeEventListener('mousemove', this.handleMouseMove);\n      this.host.removeEventListener('mouseover', this.handleMouseOver);\n      this.host.removeEventListener('keydown', this.handleKeyDown);\n      this.host.removeEventListener('click', this.handleClick);\n      this.host.removeEventListener('focusout', this.handleFocusOut);\n      this.isConnected = false;\n    }\n    if (this.isPopupConnected) {\n      if (this.popupRef.value) {\n        this.popupRef.value.removeEventListener('mouseover', this.handlePopupMouseover);\n        this.popupRef.value.removeEventListener('sl-reposition', this.handlePopupReposition);\n        this.isPopupConnected = false;\n      }\n    }\n  }\n\n  // Set the safe triangle cursor position\n  private handleMouseMove = (event: MouseEvent) => {\n    this.host.style.setProperty('--safe-triangle-cursor-x', `${event.clientX}px`);\n    this.host.style.setProperty('--safe-triangle-cursor-y', `${event.clientY}px`);\n  };\n\n  private handleMouseOver = () => {\n    if (this.hasSlotController.test('submenu')) {\n      this.enableSubmenu();\n    }\n  };\n\n  private handleSubmenuEntry(event: KeyboardEvent) {\n    // Pass focus to the first menu-item in the submenu.\n    const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector(\"slot[name='submenu']\");\n\n    // Missing slot\n    if (!submenuSlot) {\n      console.error('Cannot activate a submenu if no corresponding menuitem can be found.', this);\n      return;\n    }\n\n    // Menus\n    let menuItems: NodeListOf<Element> | null = null;\n    for (const elt of submenuSlot.assignedElements()) {\n      menuItems = elt.querySelectorAll(\"sl-menu-item, [role^='menuitem']\");\n      if (menuItems.length !== 0) {\n        break;\n      }\n    }\n\n    if (!menuItems || menuItems.length === 0) {\n      return;\n    }\n\n    menuItems[0].setAttribute('tabindex', '0');\n    for (let i = 1; i !== menuItems.length; ++i) {\n      menuItems[i].setAttribute('tabindex', '-1');\n    }\n\n    // Open the submenu (if not open), and set focus to first menuitem.\n    if (this.popupRef.value) {\n      event.preventDefault();\n      event.stopPropagation();\n      if (this.popupRef.value.active) {\n        if (menuItems[0] instanceof HTMLElement) {\n          menuItems[0].focus();\n        }\n      } else {\n        this.enableSubmenu(false);\n        this.host.updateComplete.then(() => {\n          if (menuItems[0] instanceof HTMLElement) {\n            menuItems[0].focus();\n          }\n        });\n        this.host.requestUpdate();\n      }\n    }\n  }\n\n  // Focus on the first menu-item of a submenu.\n  private handleKeyDown = (event: KeyboardEvent) => {\n    switch (event.key) {\n      case 'Escape':\n      case 'Tab':\n        this.disableSubmenu();\n        break;\n      case 'ArrowLeft':\n        // Either focus is currently on the host element or a child\n        if (event.target !== this.host) {\n          event.preventDefault();\n          event.stopPropagation();\n          this.host.focus();\n          this.disableSubmenu();\n        }\n        break;\n      case 'ArrowRight':\n      case 'Enter':\n      case ' ':\n        this.handleSubmenuEntry(event);\n        break;\n      default:\n        break;\n    }\n  };\n\n  private handleClick = (event: MouseEvent) => {\n    // Clicking on the item which heads the menu does nothing, otherwise hide submenu and propagate\n    if (event.target === this.host) {\n      event.preventDefault();\n      event.stopPropagation();\n    } else if (\n      event.target instanceof Element &&\n      (event.target.tagName === 'sl-menu-item' || event.target.role?.startsWith('menuitem'))\n    ) {\n      this.disableSubmenu();\n    }\n  };\n\n  // Close this submenu on focus outside of the parent or any descendants.\n  private handleFocusOut = (event: FocusEvent) => {\n    if (event.relatedTarget && event.relatedTarget instanceof Element && this.host.contains(event.relatedTarget)) {\n      return;\n    }\n    this.disableSubmenu();\n  };\n\n  // Prevent the parent menu-item from getting focus on mouse movement on the submenu\n  private handlePopupMouseover = (event: MouseEvent) => {\n    event.stopPropagation();\n  };\n\n  // Set the safe triangle values for the submenu when the position changes\n  private handlePopupReposition = () => {\n    const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector(\"slot[name='submenu']\");\n    const menu = submenuSlot?.assignedElements({ flatten: true }).filter(el => el.localName === 'sl-menu')[0];\n    const isRtl = getComputedStyle(this.host).direction === 'rtl';\n    if (!menu) {\n      return;\n    }\n\n    const { left, top, width, height } = menu.getBoundingClientRect();\n\n    this.host.style.setProperty('--safe-triangle-submenu-start-x', `${isRtl ? left + width : left}px`);\n    this.host.style.setProperty('--safe-triangle-submenu-start-y', `${top}px`);\n    this.host.style.setProperty('--safe-triangle-submenu-end-x', `${isRtl ? left + width : left}px`);\n    this.host.style.setProperty('--safe-triangle-submenu-end-y', `${top + height}px`);\n  };\n\n  private setSubmenuState(state: boolean) {\n    if (this.popupRef.value) {\n      if (this.popupRef.value.active !== state) {\n        this.popupRef.value.active = state;\n        this.host.requestUpdate();\n      }\n    }\n  }\n\n  // Shows the submenu. Supports disabling the opening delay, e.g. for keyboard events that want to set the focus to the\n  // newly opened menu.\n  private enableSubmenu(delay = true) {\n    if (delay) {\n      window.clearTimeout(this.enableSubmenuTimer);\n      this.enableSubmenuTimer = window.setTimeout(() => {\n        this.setSubmenuState(true);\n      }, this.submenuOpenDelay);\n    } else {\n      this.setSubmenuState(true);\n    }\n  }\n\n  private disableSubmenu() {\n    window.clearTimeout(this.enableSubmenuTimer);\n    this.setSubmenuState(false);\n  }\n\n  // Calculate the space the top of a menu takes-up, for aligning the popup menu-item with the activating element.\n  private updateSkidding(): void {\n    // .computedStyleMap() not always available.\n    if (!this.host.parentElement?.computedStyleMap) {\n      return;\n    }\n    const styleMap: StylePropertyMapReadOnly = this.host.parentElement.computedStyleMap();\n    const attrs: string[] = ['padding-top', 'border-top-width', 'margin-top'];\n\n    const skidding = attrs.reduce((accumulator, attr) => {\n      const styleValue: CSSStyleValue = styleMap.get(attr) ?? new CSSUnitValue(0, 'px');\n      const unitValue = styleValue instanceof CSSUnitValue ? styleValue : new CSSUnitValue(0, 'px');\n      const pxValue = unitValue.to('px');\n      return accumulator - pxValue.value;\n    }, 0);\n\n    this.skidding = skidding;\n  }\n\n  isExpanded(): boolean {\n    return this.popupRef.value ? this.popupRef.value.active : false;\n  }\n\n  renderSubmenu() {\n    const isRtl = getComputedStyle(this.host).direction === 'rtl';\n\n    // Always render the slot, but conditionally render the outer <sl-popup>\n    if (!this.isConnected) {\n      return html` <slot name=\"submenu\" hidden></slot> `;\n    }\n\n    return html`\n      <sl-popup\n        ${ref(this.popupRef)}\n        placement=${isRtl ? 'left-start' : 'right-start'}\n        anchor=\"anchor\"\n        flip\n        flip-fallback-strategy=\"best-fit\"\n        skidding=\"${this.skidding}\"\n        strategy=\"fixed\"\n        auto-size=\"vertical\"\n        auto-size-padding=\"10\"\n      >\n        <slot name=\"submenu\"></slot>\n      </sl-popup>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/menu-label/menu-label.component.ts",
    "content": "import { html } from 'lit';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './menu-label.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Menu labels are used to describe a group of menu items.\n * @documentation https://shoelace.style/components/menu-label\n * @status stable\n * @since 2.0\n *\n * @slot - The menu label's content.\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlMenuLabel extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  render() {\n    return html` <slot part=\"base\" class=\"menu-label\"></slot> `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-menu-label': SlMenuLabel;\n  }\n}\n"
  },
  {
    "path": "src/components/menu-label/menu-label.styles.ts",
    "content": "import { css } from 'lit';\nexport default css`\n  :host {\n    display: block;\n  }\n\n  .menu-label {\n    display: inline-block;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-small);\n    font-weight: var(--sl-font-weight-semibold);\n    line-height: var(--sl-line-height-normal);\n    letter-spacing: var(--sl-letter-spacing-normal);\n    color: var(--sl-color-neutral-500);\n    padding: var(--sl-spacing-2x-small) var(--sl-spacing-x-large);\n    user-select: none;\n    -webkit-user-select: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/menu-label/menu-label.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlMenuLabel from './menu-label.js';\n\ndescribe('<sl-menu-label>', () => {\n  it('passes accessibility test', async () => {\n    const el = await fixture<SlMenuLabel>(html` <sl-menu-label>Test</sl-menu-label> `);\n    await expect(el).to.be.accessible();\n  });\n});\n"
  },
  {
    "path": "src/components/menu-label/menu-label.ts",
    "content": "import SlMenuLabel from './menu-label.component.js';\n\nexport * from './menu-label.component.js';\nexport default SlMenuLabel;\n\nSlMenuLabel.define('sl-menu-label');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-menu-label': SlMenuLabel;\n  }\n}\n"
  },
  {
    "path": "src/components/mutation-observer/mutation-observer.component.ts",
    "content": "import { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './mutation-observer.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary The Mutation Observer component offers a thin, declarative interface to the [`MutationObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).\n * @documentation https://shoelace.style/components/mutation-observer\n * @status stable\n * @since 2.0\n *\n * @event {{ mutationList: MutationRecord[] }} sl-mutation - Emitted when a mutation occurs.\n *\n * @slot - The content to watch for mutations.\n */\nexport default class SlMutationObserver extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private mutationObserver: MutationObserver;\n\n  /**\n   * Watches for changes to attributes. To watch only specific attributes, separate them by a space, e.g.\n   * `attr=\"class id title\"`. To watch all attributes, use `*`.\n   */\n  @property({ reflect: true }) attr: string;\n\n  /** Indicates whether or not the attribute's previous value should be recorded when monitoring changes. */\n  @property({ attribute: 'attr-old-value', type: Boolean, reflect: true }) attrOldValue = false;\n\n  /** Watches for changes to the character data contained within the node. */\n  @property({ attribute: 'char-data', type: Boolean, reflect: true }) charData = false;\n\n  /** Indicates whether or not the previous value of the node's text should be recorded. */\n  @property({ attribute: 'char-data-old-value', type: Boolean, reflect: true }) charDataOldValue = false;\n\n  /** Watches for the addition or removal of new child nodes. */\n  @property({ attribute: 'child-list', type: Boolean, reflect: true }) childList = false;\n\n  /** Disables the observer. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n\n    this.mutationObserver = new MutationObserver(this.handleMutation);\n\n    if (!this.disabled) {\n      this.startObserver();\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.stopObserver();\n  }\n\n  private handleMutation = (mutationList: MutationRecord[]) => {\n    this.emit('sl-mutation', {\n      detail: { mutationList }\n    });\n  };\n\n  private startObserver() {\n    const observeAttributes = typeof this.attr === 'string' && this.attr.length > 0;\n    const attributeFilter = observeAttributes && this.attr !== '*' ? this.attr.split(' ') : undefined;\n\n    try {\n      this.mutationObserver.observe(this, {\n        subtree: true,\n        childList: this.childList,\n        attributes: observeAttributes,\n        attributeFilter,\n        attributeOldValue: this.attrOldValue,\n        characterData: this.charData,\n        characterDataOldValue: this.charDataOldValue\n      });\n    } catch {\n      //\n      // A mutation observer was created without one of the required attributes: attr, char-data, or child-list. The\n      // browser will normally throw an error, but we'll suppress that so it doesn't appear as attributes are added\n      // and removed.\n      //\n    }\n  }\n\n  private stopObserver() {\n    this.mutationObserver.disconnect();\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    if (this.disabled) {\n      this.stopObserver();\n    } else {\n      this.startObserver();\n    }\n  }\n\n  @watch('attr', { waitUntilFirstUpdate: true })\n  @watch('attr-old-value', { waitUntilFirstUpdate: true })\n  @watch('char-data', { waitUntilFirstUpdate: true })\n  @watch('char-data-old-value', { waitUntilFirstUpdate: true })\n  @watch('childList', { waitUntilFirstUpdate: true })\n  handleChange() {\n    this.stopObserver();\n    this.startObserver();\n  }\n\n  render() {\n    return html` <slot></slot> `;\n  }\n}\n"
  },
  {
    "path": "src/components/mutation-observer/mutation-observer.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: contents;\n  }\n`;\n"
  },
  {
    "path": "src/components/mutation-observer/mutation-observer.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\n\ndescribe('<sl-mutation-observer>', () => {\n  it('should render a component', async () => {\n    const el = await fixture(html` <sl-mutation-observer></sl-mutation-observer> `);\n\n    expect(el).to.exist;\n  });\n});\n"
  },
  {
    "path": "src/components/mutation-observer/mutation-observer.ts",
    "content": "import SlMutationObserver from './mutation-observer.component.js';\n\nexport * from './mutation-observer.component.js';\nexport default SlMutationObserver;\n\nSlMutationObserver.define('sl-mutation-observer');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-mutation-observer': SlMutationObserver;\n  }\n}\n"
  },
  {
    "path": "src/components/option/option.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './option.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Options define the selectable items within various form controls such as [select](/components/select).\n * @documentation https://shoelace.style/components/option\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot - The option's label.\n * @slot prefix - Used to prepend an icon or similar element to the menu item.\n * @slot suffix - Used to append an icon or similar element to the menu item.\n *\n * @csspart checked-icon - The checked icon, an `<sl-icon>` element.\n * @csspart base - The component's base wrapper.\n * @csspart label - The option's label.\n * @csspart prefix - The container that wraps the prefix.\n * @csspart suffix - The container that wraps the suffix.\n */\nexport default class SlOption extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  // @ts-expect-error - Controller is currently unused\n  private readonly localize = new LocalizeController(this);\n\n  private isInitialized = false;\n\n  @query('.option__label') defaultSlot: HTMLSlotElement;\n\n  @state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)\n  @state() selected = false; // the option is selected and has aria-selected=\"true\"\n  @state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging\n\n  /**\n   * The option's value. When selected, the containing form control will receive this value. The value must be unique\n   * from other options in the same group. Values may not contain spaces, as spaces are used as delimiters when listing\n   * multiple values.\n   */\n  @property({ reflect: true }) value = '';\n\n  /** Draws the option in a disabled state, preventing selection. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setAttribute('role', 'option');\n    this.setAttribute('aria-selected', 'false');\n  }\n\n  private handleDefaultSlotChange() {\n    if (this.isInitialized) {\n      // When the label changes, tell the controller to update\n      customElements.whenDefined('sl-select').then(() => {\n        const controller = this.closest('sl-select');\n        if (controller) {\n          controller.handleDefaultSlotChange();\n        }\n      });\n    } else {\n      this.isInitialized = true;\n    }\n  }\n\n  private handleMouseEnter() {\n    this.hasHover = true;\n  }\n\n  private handleMouseLeave() {\n    this.hasHover = false;\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  @watch('selected')\n  handleSelectedChange() {\n    this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n  }\n\n  @watch('value')\n  handleValueChange() {\n    // Ensure the value is a string. This ensures the next line doesn't error and allows framework users to pass numbers\n    // instead of requiring them to cast the value to a string.\n    if (typeof this.value !== 'string') {\n      this.value = String(this.value);\n    }\n\n    if (this.value.includes(' ')) {\n      console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);\n      this.value = this.value.replace(/ /g, '_');\n    }\n  }\n\n  /** Returns a plain text label based on the option's content. */\n  getTextLabel() {\n    const nodes = this.childNodes;\n    let label = '';\n\n    [...nodes].forEach(node => {\n      if (node.nodeType === Node.ELEMENT_NODE) {\n        if (!(node as HTMLElement).hasAttribute('slot')) {\n          label += (node as HTMLElement).textContent;\n        }\n      }\n\n      if (node.nodeType === Node.TEXT_NODE) {\n        label += node.textContent;\n      }\n    });\n\n    return label.trim();\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          option: true,\n          'option--current': this.current,\n          'option--disabled': this.disabled,\n          'option--selected': this.selected,\n          'option--hover': this.hasHover\n        })}\n        @mouseenter=${this.handleMouseEnter}\n        @mouseleave=${this.handleMouseLeave}\n      >\n        <sl-icon part=\"checked-icon\" class=\"option__check\" name=\"check\" library=\"system\" aria-hidden=\"true\"></sl-icon>\n        <slot part=\"prefix\" name=\"prefix\" class=\"option__prefix\"></slot>\n        <slot part=\"label\" class=\"option__label\" @slotchange=${this.handleDefaultSlotChange}></slot>\n        <slot part=\"suffix\" name=\"suffix\" class=\"option__suffix\"></slot>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/option/option.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  :host(:focus) {\n    outline: none;\n  }\n\n  .option {\n    position: relative;\n    display: flex;\n    align-items: center;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    line-height: var(--sl-line-height-normal);\n    letter-spacing: var(--sl-letter-spacing-normal);\n    color: var(--sl-color-neutral-700);\n    padding: var(--sl-spacing-x-small) var(--sl-spacing-medium) var(--sl-spacing-x-small) var(--sl-spacing-x-small);\n    transition: var(--sl-transition-fast) fill;\n    cursor: pointer;\n  }\n\n  .option--hover:not(.option--current):not(.option--disabled) {\n    background-color: var(--sl-color-neutral-100);\n    color: var(--sl-color-neutral-1000);\n  }\n\n  .option--current,\n  .option--current.option--disabled {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n    opacity: 1;\n  }\n\n  .option--disabled {\n    outline: none;\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .option__label {\n    flex: 1 1 auto;\n    display: inline-block;\n    line-height: var(--sl-line-height-dense);\n  }\n\n  .option .option__check {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    visibility: hidden;\n    padding-inline-end: var(--sl-spacing-2x-small);\n  }\n\n  .option--selected .option__check {\n    visibility: visible;\n  }\n\n  .option__prefix,\n  .option__suffix {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n  }\n\n  .option__prefix::slotted(*) {\n    margin-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .option__suffix::slotted(*) {\n    margin-inline-start: var(--sl-spacing-x-small);\n  }\n\n  @media (forced-colors: active) {\n    :host(:hover:not([aria-disabled='true'])) .option {\n      outline: dashed 1px SelectedItem;\n      outline-offset: -1px;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/option/option.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html } from '@open-wc/testing';\nimport type SlOption from './option.js';\n\ndescribe('<sl-option>', () => {\n  it('passes accessibility test', async () => {\n    const el = await fixture<SlOption>(html`\n      <sl-select label=\"Select one\">\n        <sl-option value=\"1\">Option 1</sl-option>\n        <sl-option value=\"2\">Option 2</sl-option>\n        <sl-option value=\"3\">Option 3</sl-option>\n        <sl-option value=\"4\" disabled>Disabled</sl-option>\n      </sl-select>\n    `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlOption>(html` <sl-option>Test</sl-option> `);\n\n    expect(el.value).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.getAttribute('aria-disabled')).to.equal('false');\n  });\n\n  it('changes aria attributes', async () => {\n    const el = await fixture<SlOption>(html` <sl-option>Test</sl-option> `);\n\n    el.disabled = true;\n    await aTimeout(100);\n    expect(el.getAttribute('aria-disabled')).to.equal('true');\n  });\n\n  it('should convert non-string values to string', async () => {\n    const el = await fixture<SlOption>(html` <sl-option>Text</sl-option> `);\n\n    // @ts-expect-error - intentional\n    el.value = 10;\n    await el.updateComplete;\n\n    expect(el.value).to.equal('10');\n  });\n\n  it('should escape HTML when calling getTextLabel()', async () => {\n    const el = await fixture<SlOption>(html` <sl-option><strong>Option</strong></sl-option> `);\n    expect(el.getTextLabel()).to.equal('Option');\n  });\n});\n"
  },
  {
    "path": "src/components/option/option.ts",
    "content": "import SlOption from './option.component.js';\n\nexport * from './option.component.js';\nexport default SlOption;\n\nSlOption.define('sl-option');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-option': SlOption;\n  }\n}\n"
  },
  {
    "path": "src/components/popup/popup.component.ts",
    "content": "import { arrow, autoUpdate, computePosition, flip, offset, platform, shift, size } from '@floating-ui/dom';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { offsetParent } from 'composed-offset-position';\nimport { property, query } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './popup.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\nexport interface VirtualElement {\n  getBoundingClientRect: () => DOMRect;\n  contextElement?: Element;\n}\n\nfunction isVirtualElement(e: unknown): e is VirtualElement {\n  return (\n    e !== null &&\n    typeof e === 'object' &&\n    'getBoundingClientRect' in e &&\n    ('contextElement' in e ? e.contextElement instanceof Element : true)\n  );\n}\n\n/**\n * @summary Popup is a utility that lets you declaratively anchor \"popup\" containers to another element.\n * @documentation https://shoelace.style/components/popup\n * @status stable\n * @since 2.0\n *\n * @event sl-reposition - Emitted when the popup is repositioned. This event can fire a lot, so avoid putting expensive\n *  operations in your listener or consider debouncing it.\n *\n * @slot - The popup's content.\n * @slot anchor - The element the popup will be anchored to. If the anchor lives outside of the popup, you can use the\n *  `anchor` attribute or property instead.\n *\n * @csspart arrow - The arrow's container. Avoid setting `top|bottom|left|right` properties, as these values are\n *  assigned dynamically as the popup moves. This is most useful for applying a background color to match the popup, and\n *  maybe a border or box shadow.\n * @csspart popup - The popup's container. Useful for setting a background color, box shadow, etc.\n * @csspart hover-bridge - The hover bridge element. Only available when the `hover-bridge` option is enabled.\n *\n * @cssproperty [--arrow-size=6px] - The size of the arrow. Note that an arrow won't be shown unless the `arrow`\n *  attribute is used.\n * @cssproperty [--arrow-color=var(--sl-color-neutral-0)] - The color of the arrow.\n * @cssproperty [--auto-size-available-width] - A read-only custom property that determines the amount of width the\n *  popup can be before overflowing. Useful for positioning child elements that need to overflow. This property is only\n *  available when using `auto-size`.\n * @cssproperty [--auto-size-available-height] - A read-only custom property that determines the amount of height the\n *  popup can be before overflowing. Useful for positioning child elements that need to overflow. This property is only\n *  available when using `auto-size`.\n */\nexport default class SlPopup extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private anchorEl: Element | VirtualElement | null;\n  private cleanup: ReturnType<typeof autoUpdate> | undefined;\n  private readonly localize = new LocalizeController(this);\n\n  /** A reference to the internal popup container. Useful for animating and styling the popup with JavaScript. */\n  @query('.popup') popup: HTMLElement;\n  @query('.popup__arrow') private arrowEl: HTMLElement;\n\n  /**\n   * The element the popup will be anchored to. If the anchor lives outside of the popup, you can provide the anchor\n   * element `id`, a DOM element reference, or a `VirtualElement`. If the anchor lives inside the popup, use the\n   * `anchor` slot instead.\n   */\n  @property() anchor: Element | string | VirtualElement;\n\n  /**\n   * Activates the positioning logic and shows the popup. When this attribute is removed, the positioning logic is torn\n   * down and the popup will be hidden.\n   */\n  @property({ type: Boolean, reflect: true }) active = false;\n\n  /**\n   * The preferred placement of the popup. Note that the actual placement will vary as configured to keep the\n   * panel inside of the viewport.\n   */\n  @property({ reflect: true }) placement:\n    | 'top'\n    | 'top-start'\n    | 'top-end'\n    | 'bottom'\n    | 'bottom-start'\n    | 'bottom-end'\n    | 'right'\n    | 'right-start'\n    | 'right-end'\n    | 'left'\n    | 'left-start'\n    | 'left-end' = 'top';\n\n  /**\n   * Determines how the popup is positioned. The `absolute` strategy works well in most cases, but if overflow is\n   * clipped, using a `fixed` position strategy can often workaround it.\n   */\n  @property({ reflect: true }) strategy: 'absolute' | 'fixed' = 'absolute';\n\n  /** The distance in pixels from which to offset the panel away from its anchor. */\n  @property({ type: Number }) distance = 0;\n\n  /** The distance in pixels from which to offset the panel along its anchor. */\n  @property({ type: Number }) skidding = 0;\n\n  /**\n   * Attaches an arrow to the popup. The arrow's size and color can be customized using the `--arrow-size` and\n   * `--arrow-color` custom properties. For additional customizations, you can also target the arrow using\n   * `::part(arrow)` in your stylesheet.\n   */\n  @property({ type: Boolean }) arrow = false;\n\n  /**\n   * The placement of the arrow. The default is `anchor`, which will align the arrow as close to the center of the\n   * anchor as possible, considering available space and `arrow-padding`. A value of `start`, `end`, or `center` will\n   * align the arrow to the start, end, or center of the popover instead.\n   */\n  @property({ attribute: 'arrow-placement' }) arrowPlacement: 'start' | 'end' | 'center' | 'anchor' = 'anchor';\n\n  /**\n   * The amount of padding between the arrow and the edges of the popup. If the popup has a border-radius, for example,\n   * this will prevent it from overflowing the corners.\n   */\n  @property({ attribute: 'arrow-padding', type: Number }) arrowPadding = 10;\n\n  /**\n   * When set, placement of the popup will flip to the opposite site to keep it in view. You can use\n   * `flipFallbackPlacements` to further configure how the fallback placement is determined.\n   */\n  @property({ type: Boolean }) flip = false;\n\n  /**\n   * If the preferred placement doesn't fit, popup will be tested in these fallback placements until one fits. Must be a\n   * string of any number of placements separated by a space, e.g. \"top bottom left\". If no placement fits, the flip\n   * fallback strategy will be used instead.\n   * */\n  @property({\n    attribute: 'flip-fallback-placements',\n    converter: {\n      fromAttribute: (value: string) => {\n        return value\n          .split(' ')\n          .map(p => p.trim())\n          .filter(p => p !== '');\n      },\n      toAttribute: (value: []) => {\n        return value.join(' ');\n      }\n    }\n  })\n  flipFallbackPlacements = '';\n\n  /**\n   * When neither the preferred placement nor the fallback placements fit, this value will be used to determine whether\n   * the popup should be positioned using the best available fit based on available space or as it was initially\n   * preferred.\n   */\n  @property({ attribute: 'flip-fallback-strategy' }) flipFallbackStrategy: 'best-fit' | 'initial' = 'best-fit';\n\n  /**\n   * The flip boundary describes clipping element(s) that overflow will be checked relative to when flipping. By\n   * default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can\n   * change the boundary by passing a reference to one or more elements to this property.\n   */\n  @property({ type: Object }) flipBoundary: Element | Element[];\n\n  /** The amount of padding, in pixels, to exceed before the flip behavior will occur. */\n  @property({ attribute: 'flip-padding', type: Number }) flipPadding = 0;\n\n  /** Moves the popup along the axis to keep it in view when clipped. */\n  @property({ type: Boolean }) shift = false;\n\n  /**\n   * The shift boundary describes clipping element(s) that overflow will be checked relative to when shifting. By\n   * default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can\n   * change the boundary by passing a reference to one or more elements to this property.\n   */\n  @property({ type: Object }) shiftBoundary: Element | Element[];\n\n  /** The amount of padding, in pixels, to exceed before the shift behavior will occur. */\n  @property({ attribute: 'shift-padding', type: Number }) shiftPadding = 0;\n\n  /** When set, this will cause the popup to automatically resize itself to prevent it from overflowing. */\n  @property({ attribute: 'auto-size' }) autoSize: 'horizontal' | 'vertical' | 'both';\n\n  /** Syncs the popup's width or height to that of the anchor element. */\n  @property() sync: 'width' | 'height' | 'both';\n\n  /**\n   * The auto-size boundary describes clipping element(s) that overflow will be checked relative to when resizing. By\n   * default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can\n   * change the boundary by passing a reference to one or more elements to this property.\n   */\n  @property({ type: Object }) autoSizeBoundary: Element | Element[];\n\n  /** The amount of padding, in pixels, to exceed before the auto-size behavior will occur. */\n  @property({ attribute: 'auto-size-padding', type: Number }) autoSizePadding = 0;\n\n  /**\n   * When a gap exists between the anchor and the popup element, this option will add a \"hover bridge\" that fills the\n   * gap using an invisible element. This makes listening for events such as `mouseenter` and `mouseleave` more sane\n   * because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is\n   * active.\n   */\n  @property({ attribute: 'hover-bridge', type: Boolean }) hoverBridge = false;\n\n  async connectedCallback() {\n    super.connectedCallback();\n\n    // Start the positioner after the first update\n    await this.updateComplete;\n    this.start();\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.stop();\n  }\n\n  async updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps);\n\n    // Start or stop the positioner when active changes\n    if (changedProps.has('active')) {\n      if (this.active) {\n        this.start();\n      } else {\n        this.stop();\n      }\n    }\n\n    // Update the anchor when anchor changes\n    if (changedProps.has('anchor')) {\n      this.handleAnchorChange();\n    }\n\n    // All other properties will trigger a reposition when active\n    if (this.active) {\n      await this.updateComplete;\n      this.reposition();\n    }\n  }\n\n  private async handleAnchorChange() {\n    await this.stop();\n\n    if (this.anchor && typeof this.anchor === 'string') {\n      // Locate the anchor by id\n      const root = this.getRootNode() as Document | ShadowRoot;\n      this.anchorEl = root.getElementById(this.anchor);\n    } else if (this.anchor instanceof Element || isVirtualElement(this.anchor)) {\n      // Use the anchor's reference\n      this.anchorEl = this.anchor;\n    } else {\n      // Look for a slotted anchor\n      this.anchorEl = this.querySelector<HTMLElement>('[slot=\"anchor\"]');\n    }\n\n    // If the anchor is a <slot>, we'll use the first assigned element as the target since slots use `display: contents`\n    // and positioning can't be calculated on them\n    if (this.anchorEl instanceof HTMLSlotElement) {\n      this.anchorEl = this.anchorEl.assignedElements({ flatten: true })[0] as HTMLElement;\n    }\n\n    // If the anchor is valid, start it up\n    if (this.anchorEl && this.active) {\n      this.start();\n    }\n  }\n\n  private start() {\n    // We can't start the positioner without an anchor or when the popup is inactive\n    if (!this.anchorEl || !this.active) {\n      return;\n    }\n\n    this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {\n      this.reposition();\n    });\n  }\n\n  private async stop(): Promise<void> {\n    return new Promise(resolve => {\n      if (this.cleanup) {\n        this.cleanup();\n        this.cleanup = undefined;\n        this.removeAttribute('data-current-placement');\n        this.style.removeProperty('--auto-size-available-width');\n        this.style.removeProperty('--auto-size-available-height');\n        requestAnimationFrame(() => resolve());\n      } else {\n        resolve();\n      }\n    });\n  }\n\n  /** Forces the popup to recalculate and reposition itself. */\n  reposition() {\n    // Nothing to do if the popup is inactive or the anchor doesn't exist\n    if (!this.active || !this.anchorEl) {\n      return;\n    }\n\n    //\n    // NOTE: Floating UI middlewares are order dependent: https://floating-ui.com/docs/middleware\n    //\n    const middleware = [\n      // The offset middleware goes first\n      offset({ mainAxis: this.distance, crossAxis: this.skidding })\n    ];\n\n    // First we sync width/height\n    if (this.sync) {\n      middleware.push(\n        size({\n          apply: ({ rects }) => {\n            const syncWidth = this.sync === 'width' || this.sync === 'both';\n            const syncHeight = this.sync === 'height' || this.sync === 'both';\n            this.popup.style.width = syncWidth ? `${rects.reference.width}px` : '';\n            this.popup.style.height = syncHeight ? `${rects.reference.height}px` : '';\n          }\n        })\n      );\n    } else {\n      // Cleanup styles if we're not matching width/height\n      this.popup.style.width = '';\n      this.popup.style.height = '';\n    }\n\n    // Then we flip\n    if (this.flip) {\n      middleware.push(\n        flip({\n          boundary: this.flipBoundary,\n          // @ts-expect-error - We're converting a string attribute to an array here\n          fallbackPlacements: this.flipFallbackPlacements,\n          fallbackStrategy: this.flipFallbackStrategy === 'best-fit' ? 'bestFit' : 'initialPlacement',\n          padding: this.flipPadding\n        })\n      );\n    }\n\n    // Then we shift\n    if (this.shift) {\n      middleware.push(\n        shift({\n          boundary: this.shiftBoundary,\n          padding: this.shiftPadding\n        })\n      );\n    }\n\n    // Now we adjust the size as needed\n    if (this.autoSize) {\n      middleware.push(\n        size({\n          boundary: this.autoSizeBoundary,\n          padding: this.autoSizePadding,\n          apply: ({ availableWidth, availableHeight }) => {\n            if (this.autoSize === 'vertical' || this.autoSize === 'both') {\n              this.style.setProperty('--auto-size-available-height', `${availableHeight}px`);\n            } else {\n              this.style.removeProperty('--auto-size-available-height');\n            }\n\n            if (this.autoSize === 'horizontal' || this.autoSize === 'both') {\n              this.style.setProperty('--auto-size-available-width', `${availableWidth}px`);\n            } else {\n              this.style.removeProperty('--auto-size-available-width');\n            }\n          }\n        })\n      );\n    } else {\n      // Cleanup styles if we're no longer using auto-size\n      this.style.removeProperty('--auto-size-available-width');\n      this.style.removeProperty('--auto-size-available-height');\n    }\n\n    // Finally, we add an arrow\n    if (this.arrow) {\n      middleware.push(\n        arrow({\n          element: this.arrowEl,\n          padding: this.arrowPadding\n        })\n      );\n    }\n\n    //\n    // Use custom positioning logic if the strategy is absolute. Otherwise, fall back to the default logic.\n    //\n    // More info: https://github.com/shoelace-style/shoelace/issues/1135\n    //\n    const getOffsetParent =\n      this.strategy === 'absolute'\n        ? (element: Element) => platform.getOffsetParent(element, offsetParent)\n        : platform.getOffsetParent;\n\n    computePosition(this.anchorEl, this.popup, {\n      placement: this.placement,\n      middleware,\n      strategy: this.strategy,\n      platform: {\n        ...platform,\n        getOffsetParent\n      }\n    }).then(({ x, y, middlewareData, placement }) => {\n      //\n      // Even though we have our own localization utility, it uses different heuristics to determine RTL. Because of\n      // that, we'll use the same approach that Floating UI uses.\n      //\n      // Source: https://github.com/floating-ui/floating-ui/blob/cb3b6ab07f95275730d3e6e46c702f8d4908b55c/packages/dom/src/utils/getDocumentRect.ts#L31\n      //\n      const isRtl = this.localize.dir() === 'rtl';\n      const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[placement.split('-')[0]]!;\n\n      this.setAttribute('data-current-placement', placement);\n\n      Object.assign(this.popup.style, {\n        left: `${x}px`,\n        top: `${y}px`\n      });\n\n      if (this.arrow) {\n        const arrowX = middlewareData.arrow!.x;\n        const arrowY = middlewareData.arrow!.y;\n        let top = '';\n        let right = '';\n        let bottom = '';\n        let left = '';\n\n        if (this.arrowPlacement === 'start') {\n          // Start\n          const value = typeof arrowX === 'number' ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : '';\n          top = typeof arrowY === 'number' ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : '';\n          right = isRtl ? value : '';\n          left = isRtl ? '' : value;\n        } else if (this.arrowPlacement === 'end') {\n          // End\n          const value = typeof arrowX === 'number' ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : '';\n          right = isRtl ? '' : value;\n          left = isRtl ? value : '';\n          bottom = typeof arrowY === 'number' ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : '';\n        } else if (this.arrowPlacement === 'center') {\n          // Center\n          left = typeof arrowX === 'number' ? `calc(50% - var(--arrow-size-diagonal))` : '';\n          top = typeof arrowY === 'number' ? `calc(50% - var(--arrow-size-diagonal))` : '';\n        } else {\n          // Anchor (default)\n          left = typeof arrowX === 'number' ? `${arrowX}px` : '';\n          top = typeof arrowY === 'number' ? `${arrowY}px` : '';\n        }\n\n        Object.assign(this.arrowEl.style, {\n          top,\n          right,\n          bottom,\n          left,\n          [staticSide]: 'calc(var(--arrow-size-diagonal) * -1)'\n        });\n      }\n    });\n\n    // Wait until the new position is drawn before updating the hover bridge, otherwise it can get out of sync\n    requestAnimationFrame(() => this.updateHoverBridge());\n\n    this.emit('sl-reposition');\n  }\n\n  private updateHoverBridge = () => {\n    if (this.hoverBridge && this.anchorEl) {\n      const anchorRect = this.anchorEl.getBoundingClientRect();\n      const popupRect = this.popup.getBoundingClientRect();\n      const isVertical = this.placement.includes('top') || this.placement.includes('bottom');\n      let topLeftX = 0;\n      let topLeftY = 0;\n      let topRightX = 0;\n      let topRightY = 0;\n      let bottomLeftX = 0;\n      let bottomLeftY = 0;\n      let bottomRightX = 0;\n      let bottomRightY = 0;\n\n      if (isVertical) {\n        if (anchorRect.top < popupRect.top) {\n          // Anchor is above\n          topLeftX = anchorRect.left;\n          topLeftY = anchorRect.bottom;\n          topRightX = anchorRect.right;\n          topRightY = anchorRect.bottom;\n\n          bottomLeftX = popupRect.left;\n          bottomLeftY = popupRect.top;\n          bottomRightX = popupRect.right;\n          bottomRightY = popupRect.top;\n        } else {\n          // Anchor is below\n          topLeftX = popupRect.left;\n          topLeftY = popupRect.bottom;\n          topRightX = popupRect.right;\n          topRightY = popupRect.bottom;\n\n          bottomLeftX = anchorRect.left;\n          bottomLeftY = anchorRect.top;\n          bottomRightX = anchorRect.right;\n          bottomRightY = anchorRect.top;\n        }\n      } else {\n        if (anchorRect.left < popupRect.left) {\n          // Anchor is on the left\n          topLeftX = anchorRect.right;\n          topLeftY = anchorRect.top;\n          topRightX = popupRect.left;\n          topRightY = popupRect.top;\n\n          bottomLeftX = anchorRect.right;\n          bottomLeftY = anchorRect.bottom;\n          bottomRightX = popupRect.left;\n          bottomRightY = popupRect.bottom;\n        } else {\n          // Anchor is on the right\n          topLeftX = popupRect.right;\n          topLeftY = popupRect.top;\n          topRightX = anchorRect.left;\n          topRightY = anchorRect.top;\n\n          bottomLeftX = popupRect.right;\n          bottomLeftY = popupRect.bottom;\n          bottomRightX = anchorRect.left;\n          bottomRightY = anchorRect.bottom;\n        }\n      }\n\n      this.style.setProperty('--hover-bridge-top-left-x', `${topLeftX}px`);\n      this.style.setProperty('--hover-bridge-top-left-y', `${topLeftY}px`);\n      this.style.setProperty('--hover-bridge-top-right-x', `${topRightX}px`);\n      this.style.setProperty('--hover-bridge-top-right-y', `${topRightY}px`);\n      this.style.setProperty('--hover-bridge-bottom-left-x', `${bottomLeftX}px`);\n      this.style.setProperty('--hover-bridge-bottom-left-y', `${bottomLeftY}px`);\n      this.style.setProperty('--hover-bridge-bottom-right-x', `${bottomRightX}px`);\n      this.style.setProperty('--hover-bridge-bottom-right-y', `${bottomRightY}px`);\n    }\n  };\n\n  render() {\n    return html`\n      <slot name=\"anchor\" @slotchange=${this.handleAnchorChange}></slot>\n\n      <span\n        part=\"hover-bridge\"\n        class=${classMap({\n          'popup-hover-bridge': true,\n          'popup-hover-bridge--visible': this.hoverBridge && this.active\n        })}\n      ></span>\n\n      <div\n        part=\"popup\"\n        class=${classMap({\n          popup: true,\n          'popup--active': this.active,\n          'popup--fixed': this.strategy === 'fixed',\n          'popup--has-arrow': this.arrow\n        })}\n      >\n        <slot></slot>\n        ${this.arrow ? html`<div part=\"arrow\" class=\"popup__arrow\" role=\"presentation\"></div>` : ''}\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/popup/popup.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --arrow-color: var(--sl-color-neutral-1000);\n    --arrow-size: 6px;\n\n    /*\n     * These properties are computed to account for the arrow's dimensions after being rotated 45º. The constant\n     * 0.7071 is derived from sin(45), which is the diagonal size of the arrow's container after rotating.\n     */\n    --arrow-size-diagonal: calc(var(--arrow-size) * 0.7071);\n    --arrow-padding-offset: calc(var(--arrow-size-diagonal) - var(--arrow-size));\n\n    display: contents;\n  }\n\n  .popup {\n    position: absolute;\n    isolation: isolate;\n    max-width: var(--auto-size-available-width, none);\n    max-height: var(--auto-size-available-height, none);\n  }\n\n  .popup--fixed {\n    position: fixed;\n  }\n\n  .popup:not(.popup--active) {\n    display: none;\n  }\n\n  .popup__arrow {\n    position: absolute;\n    width: calc(var(--arrow-size-diagonal) * 2);\n    height: calc(var(--arrow-size-diagonal) * 2);\n    rotate: 45deg;\n    background: var(--arrow-color);\n    z-index: -1;\n  }\n\n  /* Hover bridge */\n  .popup-hover-bridge:not(.popup-hover-bridge--visible) {\n    display: none;\n  }\n\n  .popup-hover-bridge {\n    position: fixed;\n    z-index: calc(var(--sl-z-index-dropdown) - 1);\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    clip-path: polygon(\n      var(--hover-bridge-top-left-x, 0) var(--hover-bridge-top-left-y, 0),\n      var(--hover-bridge-top-right-x, 0) var(--hover-bridge-top-right-y, 0),\n      var(--hover-bridge-bottom-right-x, 0) var(--hover-bridge-bottom-right-y, 0),\n      var(--hover-bridge-bottom-left-x, 0) var(--hover-bridge-bottom-left-y, 0)\n    );\n  }\n`;\n"
  },
  {
    "path": "src/components/popup/popup.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlPopup from './popup.js';\n\ndescribe('<sl-popup>', () => {\n  let element: SlPopup;\n\n  it('should render a component', async () => {\n    const el = await fixture(html` <sl-popup></sl-popup> `);\n\n    expect(el).to.exist;\n  });\n\n  it('should properly handle positioning when active changes', async () => {\n    element = await fixture('<sl-popup></sl-popup>');\n\n    element.active = true;\n    await element.updateComplete;\n\n    // SImulate a scroll event\n    const event = new Event('scroll');\n    window.dispatchEvent(event);\n\n    element.active = false;\n    await element.updateComplete;\n\n    // The component should not throw an error when the window is scrolled\n    expect(() => {\n      element.active = true;\n      window.dispatchEvent(event);\n    }).not.to.throw();\n  });\n});\n"
  },
  {
    "path": "src/components/popup/popup.ts",
    "content": "import SlPopup from './popup.component.js';\n\nexport * from './popup.component.js';\nexport default SlPopup;\n\nSlPopup.define('sl-popup');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-popup': SlPopup;\n  }\n}\n"
  },
  {
    "path": "src/components/progress-bar/progress-bar.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property } from 'lit/decorators.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './progress-bar.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Progress bars are used to show the status of an ongoing operation.\n * @documentation https://shoelace.style/components/progress-bar\n * @status stable\n * @since 2.0\n *\n * @slot - A label to show inside the progress indicator.\n *\n * @csspart base - The component's base wrapper.\n * @csspart indicator - The progress bar's indicator.\n * @csspart label - The progress bar's label.\n *\n * @cssproperty --height - The progress bar's height.\n * @cssproperty --track-color - The color of the track.\n * @cssproperty --indicator-color - The color of the indicator.\n * @cssproperty --label-color - The color of the label.\n */\nexport default class SlProgressBar extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  private readonly localize = new LocalizeController(this);\n\n  /** The current progress as a percentage, 0 to 100. */\n  @property({ type: Number, reflect: true }) value = 0;\n\n  /** When true, percentage is ignored, the label is hidden, and the progress bar is drawn in an indeterminate state. */\n  @property({ type: Boolean, reflect: true }) indeterminate = false;\n\n  /** A custom label for assistive devices. */\n  @property() label = '';\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          'progress-bar': true,\n          'progress-bar--indeterminate': this.indeterminate,\n          'progress-bar--rtl': this.localize.dir() === 'rtl'\n        })}\n        role=\"progressbar\"\n        title=${ifDefined(this.title)}\n        aria-label=${this.label.length > 0 ? this.label : this.localize.term('progress')}\n        aria-valuemin=\"0\"\n        aria-valuemax=\"100\"\n        aria-valuenow=${this.indeterminate ? 0 : this.value}\n      >\n        <div part=\"indicator\" class=\"progress-bar__indicator\" style=${styleMap({ width: `${this.value}%` })}>\n          ${!this.indeterminate ? html` <slot part=\"label\" class=\"progress-bar__label\"></slot> ` : ''}\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/progress-bar/progress-bar.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --height: 1rem;\n    --track-color: var(--sl-color-neutral-200);\n    --indicator-color: var(--sl-color-primary-600);\n    --label-color: var(--sl-color-neutral-0);\n\n    display: block;\n  }\n\n  .progress-bar {\n    position: relative;\n    background-color: var(--track-color);\n    height: var(--height);\n    border-radius: var(--sl-border-radius-pill);\n    box-shadow: inset var(--sl-shadow-small);\n    overflow: hidden;\n  }\n\n  .progress-bar__indicator {\n    height: 100%;\n    font-family: var(--sl-font-sans);\n    font-size: 12px;\n    font-weight: var(--sl-font-weight-normal);\n    background-color: var(--indicator-color);\n    color: var(--label-color);\n    text-align: center;\n    line-height: var(--height);\n    white-space: nowrap;\n    overflow: hidden;\n    transition:\n      400ms width,\n      400ms background-color;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  /* Indeterminate */\n  .progress-bar--indeterminate .progress-bar__indicator {\n    position: absolute;\n    animation: indeterminate 2.5s infinite cubic-bezier(0.37, 0, 0.63, 1);\n  }\n\n  .progress-bar--indeterminate.progress-bar--rtl .progress-bar__indicator {\n    animation-name: indeterminate-rtl;\n  }\n\n  @media (forced-colors: active) {\n    .progress-bar {\n      outline: solid 1px SelectedItem;\n      background-color: var(--sl-color-neutral-0);\n    }\n\n    .progress-bar__indicator {\n      outline: solid 1px SelectedItem;\n      background-color: SelectedItem;\n    }\n  }\n\n  @keyframes indeterminate {\n    0% {\n      left: -50%;\n      width: 50%;\n    }\n    75%,\n    100% {\n      left: 100%;\n      width: 50%;\n    }\n  }\n\n  @keyframes indeterminate-rtl {\n    0% {\n      right: -50%;\n      width: 50%;\n    }\n    75%,\n    100% {\n      right: 100%;\n      width: 50%;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/progress-bar/progress-bar.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlProgressBar from './progress-bar.js';\n\ndescribe('<sl-progress-bar>', () => {\n  let el: SlProgressBar;\n\n  describe('when provided just a value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressBar>(html`<sl-progress-bar value=\"25\"></sl-progress-bar>`);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n\n  describe('when provided a title, and value parameter', () => {\n    let base: HTMLDivElement;\n    let indicator: HTMLDivElement;\n\n    before(async () => {\n      el = await fixture<SlProgressBar>(\n        html`<sl-progress-bar title=\"Titled Progress Ring\" value=\"25\"></sl-progress-bar>`\n      );\n      base = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      indicator = el.shadowRoot!.querySelector('[part~=\"indicator\"]')!;\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('uses the value parameter on the base, as aria-valuenow', () => {\n      expect(base).attribute('aria-valuenow', '25');\n    });\n\n    it('appends a % to the value, and uses it as the  the value parameter to determine the width on the \"indicator\" part', () => {\n      expect(indicator).attribute('style', 'width:25%;');\n    });\n  });\n\n  describe('when provided an indeterminate parameter', () => {\n    let base: HTMLDivElement;\n\n    before(async () => {\n      el = await fixture<SlProgressBar>(\n        html`<sl-progress-bar title=\"Titled Progress Ring\" indeterminate></sl-progress-bar>`\n      );\n      base = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('should append a progress-bar--indeterminate class to the \"base\" part.', () => {\n      expect(base.classList.value.trim()).to.eq('progress-bar progress-bar--indeterminate');\n    });\n  });\n\n  describe('when provided a ariaLabel, and value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressBar>(\n        html`<sl-progress-bar ariaLabel=\"Labelled Progress Ring\" value=\"25\"></sl-progress-bar>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n\n  describe('when provided a ariaLabelledBy, and value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressBar>(html`\n        <label id=\"labelledby\">Progress Ring Label</label>\n        <sl-progress-bar ariaLabelledBy=\"labelledby\" value=\"25\"></sl-progress-bar>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/progress-bar/progress-bar.ts",
    "content": "import SlProgressBar from './progress-bar.component.js';\n\nexport * from './progress-bar.component.js';\nexport default SlProgressBar;\n\nSlProgressBar.define('sl-progress-bar');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-progress-bar': SlProgressBar;\n  }\n}\n"
  },
  {
    "path": "src/components/progress-ring/progress-ring.component.ts",
    "content": "import { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './progress-ring.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Progress rings are used to show the progress of a determinate operation in a circular fashion.\n * @documentation https://shoelace.style/components/progress-ring\n * @status stable\n * @since 2.0\n *\n * @slot - A label to show inside the ring.\n *\n * @csspart base - The component's base wrapper.\n * @csspart label - The progress ring label.\n *\n * @cssproperty --size - The diameter of the progress ring (cannot be a percentage).\n * @cssproperty --track-width - The width of the track.\n * @cssproperty --track-color - The color of the track.\n * @cssproperty --indicator-width - The width of the indicator. Defaults to the track width.\n * @cssproperty --indicator-color - The color of the indicator.\n * @cssproperty --indicator-transition-duration - The duration of the indicator's transition when the value changes.\n */\nexport default class SlProgressRing extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly localize = new LocalizeController(this);\n\n  @query('.progress-ring__indicator') indicator: SVGCircleElement;\n\n  @state() indicatorOffset: string;\n\n  /** The current progress as a percentage, 0 to 100. */\n  @property({ type: Number, reflect: true }) value = 0;\n\n  /** A custom label for assistive devices. */\n  @property() label = '';\n\n  updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps);\n\n    //\n    // This block is only required for Safari because it doesn't transition the circle when the custom properties\n    // change, possibly because of a mix of pixel + unit-less values in the calc() function. It seems like a Safari bug,\n    // but I couldn't pinpoint it so this works around the problem.\n    //\n    if (changedProps.has('value')) {\n      const radius = parseFloat(getComputedStyle(this.indicator).getPropertyValue('r'));\n      const circumference = 2 * Math.PI * radius;\n      const offset = circumference - (this.value / 100) * circumference;\n\n      this.indicatorOffset = `${offset}px`;\n    }\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=\"progress-ring\"\n        role=\"progressbar\"\n        aria-label=${this.label.length > 0 ? this.label : this.localize.term('progress')}\n        aria-describedby=\"label\"\n        aria-valuemin=\"0\"\n        aria-valuemax=\"100\"\n        aria-valuenow=\"${this.value}\"\n        style=\"--percentage: ${this.value / 100}\"\n      >\n        <svg class=\"progress-ring__image\">\n          <circle class=\"progress-ring__track\"></circle>\n          <circle class=\"progress-ring__indicator\" style=\"stroke-dashoffset: ${this.indicatorOffset}\"></circle>\n        </svg>\n\n        <slot id=\"label\" part=\"label\" class=\"progress-ring__label\"></slot>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/progress-ring/progress-ring.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --size: 128px;\n    --track-width: 4px;\n    --track-color: var(--sl-color-neutral-200);\n    --indicator-width: var(--track-width);\n    --indicator-color: var(--sl-color-primary-600);\n    --indicator-transition-duration: 0.35s;\n\n    display: inline-flex;\n  }\n\n  .progress-ring {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    position: relative;\n  }\n\n  .progress-ring__image {\n    width: var(--size);\n    height: var(--size);\n    rotate: -90deg;\n    transform-origin: 50% 50%;\n  }\n\n  .progress-ring__track,\n  .progress-ring__indicator {\n    --radius: calc(var(--size) / 2 - max(var(--track-width), var(--indicator-width)) * 0.5);\n    --circumference: calc(var(--radius) * 2 * 3.141592654);\n\n    fill: none;\n    r: var(--radius);\n    cx: calc(var(--size) / 2);\n    cy: calc(var(--size) / 2);\n  }\n\n  .progress-ring__track {\n    stroke: var(--track-color);\n    stroke-width: var(--track-width);\n  }\n\n  .progress-ring__indicator {\n    stroke: var(--indicator-color);\n    stroke-width: var(--indicator-width);\n    stroke-linecap: round;\n    transition-property: stroke-dashoffset;\n    transition-duration: var(--indicator-transition-duration);\n    stroke-dasharray: var(--circumference) var(--circumference);\n    stroke-dashoffset: calc(var(--circumference) - var(--percentage) * var(--circumference));\n  }\n\n  .progress-ring__label {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    text-align: center;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/progress-ring/progress-ring.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlProgressRing from './progress-ring.js';\n\ndescribe('<sl-progress-ring>', () => {\n  let el: SlProgressRing;\n\n  describe('when provided just a value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressRing>(html`<sl-progress-ring value=\"25\"></sl-progress-ring>`);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n\n  describe('when provided a title, and value parameter', () => {\n    let base: HTMLDivElement;\n\n    before(async () => {\n      el = await fixture<SlProgressRing>(\n        html`<sl-progress-ring title=\"Titled Progress Ring\" value=\"25\"></sl-progress-ring>`\n      );\n      base = el.shadowRoot!.querySelector('[part~=\"base\"]')!;\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n\n    it('uses the value parameter on the base, as aria-valuenow', () => {\n      expect(base).attribute('aria-valuenow', '25');\n    });\n\n    it('translates the value parameter to a percentage, and uses translation on the base, as percentage css variable', () => {\n      expect(base).attribute('style', '--percentage: 0.25');\n    });\n  });\n\n  describe('when provided a ariaLabel, and value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressRing>(\n        html`<sl-progress-ring ariaLabel=\"Labelled Progress Ring\" value=\"25\"></sl-progress-ring>`\n      );\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n\n  describe('when provided a ariaLabelledBy, and value parameter', () => {\n    before(async () => {\n      el = await fixture<SlProgressRing>(html`\n        <label id=\"labelledby\">Progress Ring Label</label>\n        <sl-progress-ring ariaLabelledBy=\"labelledby\" value=\"25\"></sl-progress-ring>\n      `);\n    });\n\n    it('should pass accessibility tests', async () => {\n      await expect(el).to.be.accessible();\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/progress-ring/progress-ring.ts",
    "content": "import SlProgressRing from './progress-ring.component.js';\n\nexport * from './progress-ring.component.js';\nexport default SlProgressRing;\n\nSlProgressRing.define('sl-progress-ring');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-progress-ring': SlProgressRing;\n  }\n}\n"
  },
  {
    "path": "src/components/qr-code/qr-code.component.ts",
    "content": "import { html } from 'lit';\nimport { property, query } from 'lit/decorators.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport QrCreator from 'qr-creator';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './qr-code.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Generates a [QR code](https://www.qrcode.com/) and renders it using the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).\n * @documentation https://shoelace.style/components/qr-code\n * @status stable\n * @since 2.0\n *\n * @csspart base - The component's base wrapper.\n */\nexport default class SlQrCode extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  @query('canvas') canvas: HTMLElement;\n\n  /** The QR code's value. */\n  @property() value = '';\n\n  /** The label for assistive devices to announce. If unspecified, the value will be used instead. */\n  @property() label = '';\n\n  /** The size of the QR code, in pixels. */\n  @property({ type: Number }) size = 128;\n\n  /** The fill color. This can be any valid CSS color, but not a CSS custom property. */\n  @property() fill = 'black';\n\n  /** The background color. This can be any valid CSS color or `transparent`. It cannot be a CSS custom property. */\n  @property() background = 'white';\n\n  /** The edge radius of each module. Must be between 0 and 0.5. */\n  @property({ type: Number }) radius = 0;\n\n  /** The level of error correction to use. [Learn more](https://www.qrcode.com/en/about/error_correction.html) */\n  @property({ attribute: 'error-correction' }) errorCorrection: 'L' | 'M' | 'Q' | 'H' = 'H';\n\n  firstUpdated() {\n    this.generate();\n  }\n\n  @watch(['background', 'errorCorrection', 'fill', 'radius', 'size', 'value'])\n  generate() {\n    if (!this.hasUpdated) {\n      return;\n    }\n\n    // For some reason, when changing to \"NodeNext\", it has the wrong type for QrCreator.\n    (QrCreator as unknown as typeof QrCreator.default).render(\n      {\n        text: this.value,\n        radius: this.radius,\n        ecLevel: this.errorCorrection,\n        fill: this.fill,\n        background: this.background,\n        // We draw the canvas larger and scale its container down to avoid blurring on high-density displays\n        size: this.size * 2\n      },\n      this.canvas\n    );\n  }\n\n  render() {\n    return html`\n      <canvas\n        part=\"base\"\n        class=\"qr-code\"\n        role=\"img\"\n        aria-label=${this.label?.length > 0 ? this.label : this.value}\n        style=${styleMap({\n          width: `${this.size}px`,\n          height: `${this.size}px`\n        })}\n      ></canvas>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/qr-code/qr-code.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n`;\n"
  },
  {
    "path": "src/components/qr-code/qr-code.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlQrCode from './qr-code.js';\n\nconst getCanvas = (qrCode: SlQrCode): HTMLCanvasElement => {\n  const possibleCanvas = qrCode.shadowRoot?.querySelector<HTMLCanvasElement>('.qr-code');\n  expect(possibleCanvas).to.exist;\n  return possibleCanvas!;\n};\n\nconst expectCanvasToHaveAriaLabel = (qrCode: SlQrCode, expectedLabel: string): void => {\n  const canvas = getCanvas(qrCode);\n  expect(canvas).to.have.attribute('aria-label', expectedLabel);\n};\n\nclass Color {\n  r: number;\n  g: number;\n  b: number;\n  alpha: number;\n\n  constructor(r: number, g: number, b: number, alpha: number) {\n    this.r = r;\n    this.b = b;\n    this.g = g;\n    this.alpha = alpha;\n  }\n\n  equals(other: Color): boolean {\n    return (\n      other === this || (this.r === other.r && this.b === other.b && this.g === other.g && this.alpha === other.alpha)\n    );\n  }\n\n  toString(): string {\n    return JSON.stringify(this);\n  }\n}\n\ninterface QrCodeColors {\n  foreground: Color;\n  background: Color;\n}\n\nconst getColorFromPixel = (colorArray: Uint8ClampedArray, pixelNumber: number): Color => {\n  const startEntryNumber = pixelNumber * 4;\n  return new Color(\n    colorArray[startEntryNumber],\n    colorArray[startEntryNumber + 1],\n    colorArray[startEntryNumber + 2],\n    colorArray[startEntryNumber + 3]\n  );\n};\n\nconst getQrCodeColors = (qrCode: SlQrCode): QrCodeColors => {\n  const canvas = getCanvas(qrCode);\n  const context = canvas.getContext('2d');\n  const imageData = context?.getImageData(0, 0, canvas.width, canvas.height);\n  expect(imageData).not.to.be.null;\n  const colorArray = imageData!.data;\n  const numberOfPixels = imageData!.width * imageData!.height;\n  const foregroundColor = getColorFromPixel(colorArray, 0);\n  let backgroundColor: Color | null = null;\n  for (let pixelNumber = 0; pixelNumber < numberOfPixels; pixelNumber++) {\n    const currentColor = getColorFromPixel(colorArray, pixelNumber);\n    if (!currentColor.equals(foregroundColor)) {\n      backgroundColor = currentColor;\n      break;\n    }\n  }\n  return {\n    foreground: foregroundColor,\n    background: backgroundColor!\n  };\n};\n\nconst red = new Color(255, 0, 0, 255);\nconst white = new Color(255, 255, 255, 255);\nconst blue = new Color(0, 0, 255, 255);\n\nconst expectQrCodeColorsToBe = (qrCode: SlQrCode, expectedColors: QrCodeColors): void => {\n  const qrCodeColors = getQrCodeColors(qrCode);\n  const backgroundMessage =\n    'expected background color to be ' +\n    expectedColors.background.toString() +\n    ' but got ' +\n    qrCodeColors.background.toString();\n  expect(qrCodeColors.background.equals(expectedColors.background), backgroundMessage).to.be.true;\n  const foregroundMessage =\n    'expected foreground color to be ' +\n    expectedColors.foreground.toString() +\n    ' but got ' +\n    qrCodeColors.foreground.toString();\n  expect(qrCodeColors.foreground.equals(expectedColors.foreground), foregroundMessage).to.be.true;\n};\n\ndescribe('<sl-qr-code>', () => {\n  it('should render a component', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\"></sl-qr-code>`);\n\n    expect(qrCode).to.exist;\n  });\n\n  it('should be accessible', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\"></sl-qr-code>`);\n\n    await expect(qrCode).to.be.accessible();\n  });\n\n  it('uses the value as label if none given', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\"></sl-qr-code>`);\n\n    expectCanvasToHaveAriaLabel(qrCode, 'test data');\n  });\n\n  it('uses the label if given', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\" label=\"test label\"></sl-qr-code>`);\n\n    expectCanvasToHaveAriaLabel(qrCode, 'test label');\n  });\n\n  it('sets the correct color for the qr code', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\" fill=\"red\"></sl-qr-code>`);\n\n    expectQrCodeColorsToBe(qrCode, { foreground: red, background: white });\n  });\n\n  it('sets the correct background for the qr code', async () => {\n    const qrCode = await fixture<SlQrCode>(\n      html` <sl-qr-code value=\"test data\" fill=\"red\" background=\"blue\"></sl-qr-code>`\n    );\n\n    expectQrCodeColorsToBe(qrCode, { foreground: red, background: blue });\n  });\n\n  it('has the expected size', async () => {\n    const qrCode = await fixture<SlQrCode>(html` <sl-qr-code value=\"test data\" size=\"100\"></sl-qr-code>`);\n\n    const height = qrCode.getBoundingClientRect().height;\n    const width = qrCode.getBoundingClientRect().width;\n    expect(height).to.equal(100);\n    expect(width).to.equal(100);\n  });\n});\n"
  },
  {
    "path": "src/components/qr-code/qr-code.ts",
    "content": "import SlQrCode from './qr-code.component.js';\n\nexport * from './qr-code.component.js';\nexport default SlQrCode;\n\nSlQrCode.define('sl-qr-code');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-qr-code': SlQrCode;\n  }\n}\n"
  },
  {
    "path": "src/components/radio/radio.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './radio.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Radios allow the user to select a single option from a group.\n * @documentation https://shoelace.style/components/radio\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @slot - The radio's label.\n *\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-focus - Emitted when the control gains focus.\n *\n * @csspart base - The component's base wrapper.\n * @csspart control - The circular container that wraps the radio's checked state.\n * @csspart control--checked - The radio control when the radio is checked.\n * @csspart checked-icon - The checked icon, an `<sl-icon>` element.\n * @csspart label - The container that wraps the radio's label.\n */\nexport default class SlRadio extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  @state() checked = false;\n  @state() protected hasFocus = false;\n\n  /** The radio's value. When selected, the radio group will receive this value. */\n  @property() value: string;\n\n  /**\n   * The radio's size. When used inside a radio group, the size will be determined by the radio group's size so this\n   * attribute can typically be omitted.\n   */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Disables the radio. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  constructor() {\n    super();\n    this.addEventListener('blur', this.handleBlur);\n    this.addEventListener('click', this.handleClick);\n    this.addEventListener('focus', this.handleFocus);\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setInitialAttributes();\n  }\n\n  private handleBlur = () => {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  };\n\n  private handleClick = () => {\n    if (!this.disabled) {\n      this.checked = true;\n    }\n  };\n\n  private handleFocus = () => {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  };\n\n  private setInitialAttributes() {\n    this.setAttribute('role', 'radio');\n    this.setAttribute('tabindex', '-1');\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  @watch('checked')\n  handleCheckedChange() {\n    this.setAttribute('aria-checked', this.checked ? 'true' : 'false');\n    this.setAttribute('tabindex', this.checked ? '0' : '-1');\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  render() {\n    return html`\n      <span\n        part=\"base\"\n        class=${classMap({\n          radio: true,\n          'radio--checked': this.checked,\n          'radio--disabled': this.disabled,\n          'radio--focused': this.hasFocus,\n          'radio--small': this.size === 'small',\n          'radio--medium': this.size === 'medium',\n          'radio--large': this.size === 'large'\n        })}\n      >\n        <span part=\"${`control${this.checked ? ' control--checked' : ''}`}\" class=\"radio__control\">\n          ${this.checked\n            ? html` <sl-icon part=\"checked-icon\" class=\"radio__checked-icon\" library=\"system\" name=\"radio\"></sl-icon> `\n            : ''}\n        </span>\n\n        <slot part=\"label\" class=\"radio__label\"></slot>\n      </span>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/radio/radio.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  :host(:focus-visible) {\n    outline: 0px;\n  }\n\n  .radio {\n    display: inline-flex;\n    align-items: top;\n    font-family: var(--sl-input-font-family);\n    font-size: var(--sl-input-font-size-medium);\n    font-weight: var(--sl-input-font-weight);\n    color: var(--sl-input-label-color);\n    vertical-align: middle;\n    cursor: pointer;\n  }\n\n  .radio--small {\n    --toggle-size: var(--sl-toggle-size-small);\n    font-size: var(--sl-input-font-size-small);\n  }\n\n  .radio--medium {\n    --toggle-size: var(--sl-toggle-size-medium);\n    font-size: var(--sl-input-font-size-medium);\n  }\n\n  .radio--large {\n    --toggle-size: var(--sl-toggle-size-large);\n    font-size: var(--sl-input-font-size-large);\n  }\n\n  .radio__checked-icon {\n    display: inline-flex;\n    width: var(--toggle-size);\n    height: var(--toggle-size);\n  }\n\n  .radio__control {\n    flex: 0 0 auto;\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    width: var(--toggle-size);\n    height: var(--toggle-size);\n    border: solid var(--sl-input-border-width) var(--sl-input-border-color);\n    border-radius: 50%;\n    background-color: var(--sl-input-background-color);\n    color: transparent;\n    transition:\n      var(--sl-transition-fast) border-color,\n      var(--sl-transition-fast) background-color,\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) box-shadow;\n  }\n\n  .radio__input {\n    position: absolute;\n    opacity: 0;\n    padding: 0;\n    margin: 0;\n    pointer-events: none;\n  }\n\n  /* Hover */\n  .radio:not(.radio--checked):not(.radio--disabled) .radio__control:hover {\n    border-color: var(--sl-input-border-color-hover);\n    background-color: var(--sl-input-background-color-hover);\n  }\n\n  /* Checked */\n  .radio--checked .radio__control {\n    color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-primary-600);\n    background-color: var(--sl-color-primary-600);\n  }\n\n  /* Checked + hover */\n  .radio.radio--checked:not(.radio--disabled) .radio__control:hover {\n    border-color: var(--sl-color-primary-500);\n    background-color: var(--sl-color-primary-500);\n  }\n\n  /* Checked + focus */\n  :host(:focus-visible) .radio__control {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  /* Disabled */\n  .radio--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  /* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */\n  .radio:not(.radio--checked) svg circle {\n    opacity: 0;\n  }\n\n  .radio__label {\n    display: inline-block;\n    color: var(--sl-input-label-color);\n    line-height: var(--toggle-size);\n    margin-inline-start: 0.5em;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/radio/radio.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlRadio from './radio.js';\nimport type SlRadioGroup from '../radio-group/radio-group.js';\n\ndescribe('<sl-radio>', () => {\n  it('should not get checked when disabled', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group value=\"1\">\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\" disabled></sl-radio>\n      </sl-radio-group>\n    `);\n    const radio1 = radioGroup.querySelector<SlRadio>('#radio-1')!;\n    const radio2 = radioGroup.querySelector<SlRadio>('#radio-2')!;\n\n    radio2.click();\n    await Promise.all([radio1.updateComplete, radio2.updateComplete]);\n\n    expect(radio1.checked).to.be.true;\n    expect(radio2.checked).to.be.false;\n  });\n});\n"
  },
  {
    "path": "src/components/radio/radio.ts",
    "content": "import SlRadio from './radio.component.js';\n\nexport * from './radio.component.js';\nexport default SlRadio;\n\nSlRadio.define('sl-radio');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-radio': SlRadio;\n  }\n}\n"
  },
  {
    "path": "src/components/radio-button/radio-button.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit/static-html.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './radio-button.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Radios buttons allow the user to select a single option from a group using a button-like control.\n * @documentation https://shoelace.style/components/radio-button\n * @status stable\n * @since 2.0\n *\n * @slot - The radio button's label.\n * @slot prefix - A presentational prefix icon or similar element.\n * @slot suffix - A presentational suffix icon or similar element.\n *\n * @event sl-blur - Emitted when the button loses focus.\n * @event sl-focus - Emitted when the button gains focus.\n *\n * @csspart base - The component's base wrapper.\n * @csspart button - The internal `<button>` element.\n * @csspart button--checked - The internal button element when the radio button is checked.\n * @csspart prefix - The container that wraps the prefix.\n * @csspart label - The container that wraps the radio button's label.\n * @csspart suffix - The container that wraps the suffix.\n */\nexport default class SlRadioButton extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');\n\n  @query('.button') input: HTMLInputElement;\n  @query('.hidden-input') hiddenInput: HTMLInputElement;\n\n  @state() protected hasFocus = false;\n\n  /**\n   * @internal The radio button's checked state. This is exposed as an \"internal\" attribute so we can reflect it, making\n   * it easier to style in button groups.\n   */\n  @property({ type: Boolean, reflect: true }) checked = false;\n\n  /** The radio's value. When selected, the radio group will receive this value. */\n  @property() value: string;\n\n  /** Disables the radio button. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * The radio button's size. When used inside a radio group, the size will be determined by the radio group's size so\n   * this attribute can typically be omitted.\n   */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Draws a pill-style radio button with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setAttribute('role', 'presentation');\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleClick(e: MouseEvent) {\n    if (this.disabled) {\n      e.preventDefault();\n      e.stopPropagation();\n      return;\n    }\n\n    this.checked = true;\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  /** Sets focus on the radio button. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the radio button. */\n  blur() {\n    this.input.blur();\n  }\n\n  render() {\n    return html`\n      <div part=\"base\" role=\"presentation\">\n        <button\n          part=\"${`button${this.checked ? ' button--checked' : ''}`}\"\n          role=\"radio\"\n          aria-checked=\"${this.checked}\"\n          class=${classMap({\n            button: true,\n            'button--default': true,\n            'button--small': this.size === 'small',\n            'button--medium': this.size === 'medium',\n            'button--large': this.size === 'large',\n            'button--checked': this.checked,\n            'button--disabled': this.disabled,\n            'button--focused': this.hasFocus,\n            'button--outline': true,\n            'button--pill': this.pill,\n            'button--has-label': this.hasSlotController.test('[default]'),\n            'button--has-prefix': this.hasSlotController.test('prefix'),\n            'button--has-suffix': this.hasSlotController.test('suffix')\n          })}\n          aria-disabled=${this.disabled}\n          type=\"button\"\n          value=${ifDefined(this.value)}\n          @blur=${this.handleBlur}\n          @focus=${this.handleFocus}\n          @click=${this.handleClick}\n        >\n          <slot name=\"prefix\" part=\"prefix\" class=\"button__prefix\"></slot>\n          <slot part=\"label\" class=\"button__label\"></slot>\n          <slot name=\"suffix\" part=\"suffix\" class=\"button__suffix\"></slot>\n        </button>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/radio-button/radio-button.styles.ts",
    "content": "import { css } from 'lit';\nimport buttonStyles from '../button/button.styles.js';\n\nexport default css`\n  ${buttonStyles}\n\n  .button__prefix,\n  .button__suffix,\n  .button__label {\n    display: inline-flex;\n    position: relative;\n    align-items: center;\n  }\n\n  /* We use a hidden input so constraint validation errors work, since they don't appear to show when used with buttons.\n    We can't actually hide it, though, otherwise the messages will be suppressed by the browser. */\n  .hidden-input {\n    all: unset;\n    position: absolute;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 0;\n    outline: dotted 1px red;\n    opacity: 0;\n    z-index: -1;\n  }\n`;\n"
  },
  {
    "path": "src/components/radio-button/radio-button.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlRadioButton from './radio-button.js';\nimport type SlRadioGroup from '../radio-group/radio-group.js';\n\ndescribe('<sl-radio-button>', () => {\n  it('should not get checked when disabled', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group value=\"1\">\n        <sl-radio-button id=\"radio-1\" value=\"1\"></sl-radio-button>\n        <sl-radio-button id=\"radio-2\" value=\"2\" disabled></sl-radio-button>\n      </sl-radio-group>\n    `);\n    const radio1 = radioGroup.querySelector<SlRadioButton>('#radio-1')!;\n    const radio2 = radioGroup.querySelector<SlRadioButton>('#radio-2')!;\n\n    radio2.click();\n    await Promise.all([radio1.updateComplete, radio2.updateComplete]);\n\n    expect(radio1.checked).to.be.true;\n    expect(radio2.checked).to.be.false;\n  });\n\n  it('should receive positional data attributes from <sl-button-group>', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group value=\"1\">\n        <sl-radio-button id=\"radio-1\" value=\"1\"></sl-radio-button>\n        <sl-radio-button id=\"radio-2\" value=\"2\"></sl-radio-button>\n        <sl-radio-button id=\"radio-3\" value=\"3\"></sl-radio-button>\n      </sl-radio-group>\n    `);\n    const radio1 = radioGroup.querySelector<SlRadioButton>('#radio-1')!;\n    const radio2 = radioGroup.querySelector<SlRadioButton>('#radio-2')!;\n    const radio3 = radioGroup.querySelector<SlRadioButton>('#radio-3')!;\n\n    await Promise.all([radioGroup.updateComplete, radio1.updateComplete, radio2.updateComplete, radio3.updateComplete]);\n\n    expect(radio1).to.have.attribute('data-sl-button-group__button');\n    expect(radio1).to.have.attribute('data-sl-button-group__button--first');\n    expect(radio2).to.have.attribute('data-sl-button-group__button');\n    expect(radio2).to.have.attribute('data-sl-button-group__button--inner');\n    expect(radio3).to.have.attribute('data-sl-button-group__button');\n    expect(radio3).to.have.attribute('data-sl-button-group__button--last');\n  });\n});\n"
  },
  {
    "path": "src/components/radio-button/radio-button.ts",
    "content": "import SlRadioButton from './radio-button.component.js';\n\nexport * from './radio-button.component.js';\nexport default SlRadioButton;\n\nSlRadioButton.define('sl-radio-button');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-radio-button': SlRadioButton;\n  }\n}\n"
  },
  {
    "path": "src/components/radio-group/radio-group.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport {\n  customErrorValidityState,\n  FormControlController,\n  validValidityState,\n  valueMissingValidityState\n} from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlButtonGroup from '../button-group/button-group.component.js';\nimport styles from './radio-group.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\nimport type SlRadio from '../radio/radio.js';\nimport type SlRadioButton from '../radio-button/radio-button.js';\n\n/**\n * @summary Radio groups are used to group multiple [radios](/components/radio) or [radio buttons](/components/radio-button) so they function as a single form control.\n * @documentation https://shoelace.style/components/radio-group\n * @status stable\n * @since 2.0\n *\n * @dependency sl-button-group\n *\n * @slot - The default slot where `<sl-radio>` or `<sl-radio-button>` elements are placed.\n * @slot label - The radio group's label. Required for proper accessibility. Alternatively, you can use the `label`\n *  attribute.\n * @slot help-text - Text that describes how to use the radio group. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-change - Emitted when the radio group's selected value changes.\n * @event sl-input - Emitted when the radio group receives user input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart form-control - The form control that wraps the label, input, and help text.\n * @csspart form-control-label - The label's wrapper.\n * @csspart form-control-input - The input's wrapper.\n * @csspart form-control-help-text - The help text's wrapper.\n * @csspart button-group - The button group that wraps radio buttons.\n * @csspart button-group__base - The button group's `base` part.\n */\nexport default class SlRadioGroup extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n  static dependencies = { 'sl-button-group': SlButtonGroup };\n\n  protected readonly formControlController = new FormControlController(this);\n  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');\n  private customValidityMessage = '';\n  private validationTimeout: number;\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n  @query('.radio-group__validation-input') validationInput: HTMLInputElement;\n\n  @state() private hasButtonGroup = false;\n  @state() private errorMessage = '';\n  @state() defaultValue = '';\n\n  /**\n   * The radio group's label. Required for proper accessibility. If you need to display HTML, use the `label` slot\n   * instead.\n   */\n  @property() label = '';\n\n  /** The radio groups's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** The name of the radio group, submitted as a name/value pair with form data. */\n  @property() name = 'option';\n\n  /** The current value of the radio group, submitted as a name/value pair with form data. */\n  @property({ reflect: true }) value = '';\n\n  /** The radio group's size. This size will be applied to all child radios and radio buttons. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Ensures a child radio is checked before allowing the containing form to submit. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** Gets the validity state object */\n  get validity() {\n    const isRequiredAndEmpty = this.required && !this.value;\n    const hasCustomValidityMessage = this.customValidityMessage !== '';\n\n    if (hasCustomValidityMessage) {\n      return customErrorValidityState;\n    } else if (isRequiredAndEmpty) {\n      return valueMissingValidityState;\n    }\n\n    return validValidityState;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    const isRequiredAndEmpty = this.required && !this.value;\n    const hasCustomValidityMessage = this.customValidityMessage !== '';\n\n    if (hasCustomValidityMessage) {\n      return this.customValidityMessage;\n    } else if (isRequiredAndEmpty) {\n      return this.validationInput.validationMessage;\n    }\n\n    return '';\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.defaultValue = this.value;\n  }\n\n  firstUpdated() {\n    this.formControlController.updateValidity();\n  }\n\n  private getAllRadios() {\n    return [...this.querySelectorAll<SlRadio | SlRadioButton>('sl-radio, sl-radio-button')];\n  }\n\n  private handleRadioClick(event: MouseEvent) {\n    const target = (event.target as HTMLElement).closest<SlRadio | SlRadioButton>('sl-radio, sl-radio-button')!;\n    const radios = this.getAllRadios();\n    const oldValue = this.value;\n\n    if (!target || target.disabled) {\n      return;\n    }\n\n    this.value = target.value;\n    radios.forEach(radio => (radio.checked = radio === target));\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(event.key)) {\n      return;\n    }\n\n    const radios = this.getAllRadios().filter(radio => !radio.disabled);\n    const checkedRadio = radios.find(radio => radio.checked) ?? radios[0];\n    const incr = event.key === ' ' ? 0 : ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;\n    const oldValue = this.value;\n    let index = radios.indexOf(checkedRadio) + incr;\n\n    if (index < 0) {\n      index = radios.length - 1;\n    }\n\n    if (index > radios.length - 1) {\n      index = 0;\n    }\n\n    this.getAllRadios().forEach(radio => {\n      radio.checked = false;\n\n      if (!this.hasButtonGroup) {\n        radio.setAttribute('tabindex', '-1');\n      }\n    });\n\n    this.value = radios[index].value;\n    radios[index].checked = true;\n\n    if (!this.hasButtonGroup) {\n      radios[index].setAttribute('tabindex', '0');\n      radios[index].focus();\n    } else {\n      radios[index].shadowRoot!.querySelector('button')!.focus();\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n\n    event.preventDefault();\n  }\n\n  private handleLabelClick() {\n    this.focus();\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private async syncRadioElements() {\n    const radios = this.getAllRadios();\n\n    await Promise.all(\n      // Sync the checked state and size\n      radios.map(async radio => {\n        await radio.updateComplete;\n        radio.checked = radio.value === this.value;\n        radio.size = this.size;\n      })\n    );\n\n    this.hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'sl-radio-button');\n\n    if (radios.length > 0 && !radios.some(radio => radio.checked)) {\n      if (this.hasButtonGroup) {\n        const buttonRadio = radios[0].shadowRoot?.querySelector('button');\n\n        if (buttonRadio) {\n          buttonRadio.setAttribute('tabindex', '0');\n        }\n      } else {\n        radios[0].setAttribute('tabindex', '0');\n      }\n    }\n\n    if (this.hasButtonGroup) {\n      const buttonGroup = this.shadowRoot?.querySelector('sl-button-group');\n\n      if (buttonGroup) {\n        buttonGroup.disableRole = true;\n      }\n    }\n  }\n\n  private syncRadios() {\n    if (customElements.get('sl-radio') && customElements.get('sl-radio-button')) {\n      this.syncRadioElements();\n      return;\n    }\n\n    if (customElements.get('sl-radio')) {\n      this.syncRadioElements();\n    } else {\n      customElements.whenDefined('sl-radio').then(() => this.syncRadios());\n    }\n\n    if (customElements.get('sl-radio-button')) {\n      this.syncRadioElements();\n    } else {\n      // Rerun this handler when <sl-radio> or <sl-radio-button> is registered\n      customElements.whenDefined('sl-radio-button').then(() => this.syncRadios());\n    }\n  }\n\n  private updateCheckedRadio() {\n    const radios = this.getAllRadios();\n    radios.forEach(radio => (radio.checked = radio.value === this.value));\n    this.formControlController.setValidity(this.validity.valid);\n  }\n\n  @watch('size', { waitUntilFirstUpdate: true })\n  handleSizeChange() {\n    this.syncRadios();\n  }\n\n  @watch('value')\n  handleValueChange() {\n    if (this.hasUpdated) {\n      this.updateCheckedRadio();\n    }\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    const isRequiredAndEmpty = this.required && !this.value;\n    const hasCustomValidityMessage = this.customValidityMessage !== '';\n\n    if (isRequiredAndEmpty || hasCustomValidityMessage) {\n      this.formControlController.emitInvalidEvent();\n      return false;\n    }\n\n    return true;\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity(): boolean {\n    const isValid = this.validity.valid;\n\n    this.errorMessage = this.customValidityMessage || isValid ? '' : this.validationInput.validationMessage;\n    this.formControlController.setValidity(isValid);\n    this.validationInput.hidden = true;\n    clearTimeout(this.validationTimeout);\n\n    if (!isValid) {\n      // Show the browser's constraint validation message\n      this.validationInput.hidden = false;\n      this.validationInput.reportValidity();\n      this.validationTimeout = setTimeout(() => (this.validationInput.hidden = true), 10000) as unknown as number;\n    }\n\n    return isValid;\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message = '') {\n    this.customValidityMessage = message;\n    this.errorMessage = message;\n    this.validationInput.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  /** Sets focus on the radio-group. */\n  public focus(options?: FocusOptions) {\n    const radios = this.getAllRadios();\n    const checked = radios.find(radio => radio.checked);\n    const firstEnabledRadio = radios.find(radio => !radio.disabled);\n    const radioToFocus = checked || firstEnabledRadio;\n\n    // Call focus for the checked radio\n    // If no radio is checked, focus the first one that is not disabled\n    if (radioToFocus) {\n      radioToFocus.focus(options);\n    }\n  }\n\n  render() {\n    const hasLabelSlot = this.hasSlotController.test('label');\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasLabel = this.label ? true : !!hasLabelSlot;\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n    const defaultSlot = html`\n      <slot @slotchange=${this.syncRadios} @click=${this.handleRadioClick} @keydown=${this.handleKeyDown}></slot>\n    `;\n\n    return html`\n      <fieldset\n        part=\"form-control\"\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--radio-group': true,\n          'form-control--has-label': hasLabel,\n          'form-control--has-help-text': hasHelpText\n        })}\n        role=\"radiogroup\"\n        aria-labelledby=\"label\"\n        aria-describedby=\"help-text\"\n        aria-errormessage=\"error-message\"\n      >\n        <label\n          part=\"form-control-label\"\n          id=\"label\"\n          class=\"form-control__label\"\n          aria-hidden=${hasLabel ? 'false' : 'true'}\n          @click=${this.handleLabelClick}\n        >\n          <slot name=\"label\">${this.label}</slot>\n        </label>\n\n        <div part=\"form-control-input\" class=\"form-control-input\">\n          <div class=\"visually-hidden\">\n            <div id=\"error-message\" aria-live=\"assertive\">${this.errorMessage}</div>\n            <label class=\"radio-group__validation\">\n              <input\n                type=\"text\"\n                class=\"radio-group__validation-input\"\n                ?required=${this.required}\n                tabindex=\"-1\"\n                hidden\n                @invalid=${this.handleInvalid}\n              />\n            </label>\n          </div>\n\n          ${this.hasButtonGroup\n            ? html`\n                <sl-button-group part=\"button-group\" exportparts=\"base:button-group__base\" role=\"presentation\">\n                  ${defaultSlot}\n                </sl-button-group>\n              `\n            : defaultSlot}\n        </div>\n\n        <div\n          part=\"form-control-help-text\"\n          id=\"help-text\"\n          class=\"form-control__help-text\"\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </fieldset>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/radio-group/radio-group.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  .form-control {\n    position: relative;\n    border: none;\n    padding: 0;\n    margin: 0;\n  }\n\n  .form-control__label {\n    padding: 0;\n  }\n\n  .radio-group--required .radio-group__label::after {\n    content: var(--sl-input-required-content);\n    margin-inline-start: var(--sl-input-required-content-offset);\n  }\n\n  .visually-hidden {\n    position: absolute;\n    width: 1px;\n    height: 1px;\n    padding: 0;\n    margin: -1px;\n    overflow: hidden;\n    clip: rect(0, 0, 0, 0);\n    white-space: nowrap;\n    border: 0;\n  }\n`;\n"
  },
  {
    "path": "src/components/radio-group/radio-group.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { clickOnElement } from '../../internal/test.js';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type { SlChangeEvent } from '../../events/sl-change.js';\nimport type SlRadio from '../radio/radio.js';\nimport type SlRadioGroup from './radio-group.js';\n\ndescribe('<sl-radio-group>', () => {\n  describe('validation tests', () => {\n    it('should be invalid initially when required and no radio is checked', async () => {\n      const radioGroup = await fixture<SlRadioGroup>(html`\n        <sl-radio-group required>\n          <sl-radio value=\"1\"></sl-radio>\n          <sl-radio value=\"2\"></sl-radio>\n        </sl-radio-group>\n      `);\n\n      expect(radioGroup.checkValidity()).to.be.false;\n    });\n\n    it('should become valid when an option is checked', async () => {\n      const radioGroup = await fixture<SlRadioGroup>(html`\n        <sl-radio-group required>\n          <sl-radio value=\"1\"></sl-radio>\n          <sl-radio value=\"2\"></sl-radio>\n        </sl-radio-group>\n      `);\n\n      radioGroup.value = '1';\n      await radioGroup.updateComplete;\n\n      expect(radioGroup.checkValidity()).to.be.true;\n    });\n\n    it(`should be valid when required and one radio is checked`, async () => {\n      const el = await fixture<SlRadioGroup>(html`\n        <sl-radio-group label=\"Select an option\" value=\"1\" required>\n          <sl-radio name=\"option\" value=\"1\">Option 1</sl-radio>\n          <sl-radio name=\"option\" value=\"2\">Option 2</sl-radio>\n          <sl-radio name=\"option\" value=\"3\">Option 3</sl-radio>\n        </sl-radio-group>\n      `);\n\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it(`should be invalid when required and no radios are checked`, async () => {\n      const el = await fixture<SlRadioGroup>(html`\n        <sl-radio-group label=\"Select an option\" required>\n          <sl-radio name=\"option\" value=\"1\">Option 1</sl-radio>\n          <sl-radio name=\"option\" value=\"2\">Option 2</sl-radio>\n          <sl-radio name=\"option\" value=\"3\">Option 3</sl-radio>\n        </sl-radio-group>\n      `);\n\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it(`should be valid when required and a different radio is checked`, async () => {\n      const el = await fixture<SlRadioGroup>(html`\n        <sl-radio-group label=\"Select an option\" value=\"3\" required>\n          <sl-radio name=\"option\" value=\"1\">Option 1</sl-radio>\n          <sl-radio name=\"option\" value=\"2\">Option 2</sl-radio>\n          <sl-radio name=\"option\" value=\"3\">Option 3</sl-radio>\n        </sl-radio-group>\n      `);\n\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it(`should be invalid when custom validity is set`, async () => {\n      const el = await fixture<SlRadioGroup>(html`\n        <sl-radio-group label=\"Select an option\">\n          <sl-radio name=\"option\" value=\"1\">Option 1</sl-radio>\n          <sl-radio name=\"option\" value=\"2\">Option 2</sl-radio>\n          <sl-radio name=\"option\" value=\"3\">Option 3</sl-radio>\n        </sl-radio-group>\n      `);\n\n      el.setCustomValidity('Error');\n\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when valid', async () => {\n      const radioGroup = await fixture<SlRadioGroup>(html`\n        <sl-radio-group value=\"1\" required>\n          <sl-radio value=\"1\"></sl-radio>\n          <sl-radio value=\"2\"></sl-radio>\n        </sl-radio-group>\n      `);\n      const secondRadio = radioGroup.querySelectorAll('sl-radio')[1];\n\n      expect(radioGroup.checkValidity()).to.be.true;\n      expect(radioGroup.hasAttribute('data-required')).to.be.true;\n      expect(radioGroup.hasAttribute('data-optional')).to.be.false;\n      expect(radioGroup.hasAttribute('data-invalid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-valid')).to.be.true;\n      expect(radioGroup.hasAttribute('data-user-invalid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(secondRadio);\n      await secondRadio.updateComplete;\n\n      expect(radioGroup.checkValidity()).to.be.true;\n      expect(radioGroup.hasAttribute('data-user-invalid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-valid')).to.be.true;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when invalid', async () => {\n      const radioGroup = await fixture<SlRadioGroup>(html`\n        <sl-radio-group required>\n          <sl-radio value=\"1\"></sl-radio>\n          <sl-radio value=\"2\"></sl-radio>\n        </sl-radio-group>\n      `);\n      const secondRadio = radioGroup.querySelectorAll('sl-radio')[1];\n\n      expect(radioGroup.hasAttribute('data-required')).to.be.true;\n      expect(radioGroup.hasAttribute('data-optional')).to.be.false;\n      expect(radioGroup.hasAttribute('data-invalid')).to.be.true;\n      expect(radioGroup.hasAttribute('data-valid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-invalid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(secondRadio);\n      radioGroup.value = '';\n      await radioGroup.updateComplete;\n\n      expect(radioGroup.hasAttribute('data-user-invalid')).to.be.true;\n      expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <form novalidate>\n          <sl-radio-group required>\n            <sl-radio value=\"1\"></sl-radio>\n            <sl-radio value=\"2\"></sl-radio>\n          </sl-radio-group>\n        </form>\n      `);\n      const radioGroup = el.querySelector<SlRadioGroup>('sl-radio-group')!;\n\n      expect(radioGroup.hasAttribute('data-required')).to.be.true;\n      expect(radioGroup.hasAttribute('data-optional')).to.be.false;\n      expect(radioGroup.hasAttribute('data-invalid')).to.be.true;\n      expect(radioGroup.hasAttribute('data-valid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-invalid')).to.be.false;\n      expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should show a constraint validation error when setCustomValidity() is called', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-radio-group value=\"1\">\n            <sl-radio id=\"radio-1\" name=\"a\" value=\"1\"></sl-radio>\n            <sl-radio id=\"radio-2\" name=\"a\" value=\"2\"></sl-radio>\n          </sl-radio-group>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const radioGroup = form.querySelector<SlRadioGroup>('sl-radio-group')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => event.preventDefault());\n\n      // Submitting the form after setting custom validity should not trigger the handler\n      radioGroup.setCustomValidity('Invalid selection');\n      form.addEventListener('submit', submitHandler);\n      button.click();\n\n      await aTimeout(100);\n\n      expect(submitHandler).to.not.have.been.called;\n    });\n  });\n});\n\ndescribe('when resetting a form', () => {\n  it('should reset the element to its initial value', async () => {\n    const form = await fixture<HTMLFormElement>(html`\n      <form>\n        <sl-radio-group value=\"1\">\n          <sl-radio value=\"1\"></sl-radio>\n          <sl-radio value=\"2\"></sl-radio>\n        </sl-radio-group>\n        <sl-button type=\"reset\">Reset</sl-button>\n      </form>\n    `);\n    const button = form.querySelector('sl-button')!;\n    const radioGroup = form.querySelector('sl-radio-group')!;\n    radioGroup.value = '2';\n\n    await radioGroup.updateComplete;\n    setTimeout(() => button.click());\n\n    await oneEvent(form, 'reset');\n    await radioGroup.updateComplete;\n\n    expect(radioGroup.value).to.equal('1');\n  });\n});\n\ndescribe('when submitting a form', () => {\n  it('should submit the correct value when a value is provided', async () => {\n    const form = await fixture<HTMLFormElement>(html`\n      <form>\n        <sl-radio-group name=\"a\" value=\"1\">\n          <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n          <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n          <sl-radio id=\"radio-3\" value=\"3\"></sl-radio>\n        </sl-radio-group>\n        <sl-button type=\"submit\">Submit</sl-button>\n      </form>\n    `);\n    const button = form.querySelector('sl-button')!;\n    const radio = form.querySelectorAll('sl-radio')[1]!;\n    const submitHandler = sinon.spy((event: SubmitEvent) => {\n      formData = new FormData(form);\n\n      event.preventDefault();\n    });\n    let formData: FormData;\n\n    form.addEventListener('submit', submitHandler);\n    radio.click();\n    button.click();\n    await waitUntil(() => submitHandler.calledOnce);\n\n    expect(formData!.get('a')).to.equal('2');\n  });\n\n  it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n    const el = await fixture<HTMLFormElement>(html`\n      <div>\n        <form id=\"f\">\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n        <sl-radio-group form=\"f\" name=\"a\" value=\"1\">\n          <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n          <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n          <sl-radio id=\"radio-3\" value=\"3\"></sl-radio>\n        </sl-radio-group>\n      </div>\n    `);\n    const form = el.querySelector('form')!;\n    const formData = new FormData(form);\n\n    expect(formData.get('a')).to.equal('1');\n  });\n});\n\ndescribe('when a size is applied', () => {\n  it('should apply the same size to all radios', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group size=\"large\">\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n      </sl-radio-group>\n    `);\n    const [radio1, radio2] = radioGroup.querySelectorAll('sl-radio')!;\n\n    expect(radio1.size).to.equal('large');\n    expect(radio2.size).to.equal('large');\n  });\n\n  it('should apply the same size to all radio buttons', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group size=\"large\">\n        <sl-radio-button id=\"radio-1\" value=\"1\"></sl-radio-button>\n        <sl-radio-button id=\"radio-2\" value=\"2\"></sl-radio-button>\n      </sl-radio-group>\n    `);\n    const [radio1, radio2] = radioGroup.querySelectorAll('sl-radio-button')!;\n\n    expect(radio1.size).to.equal('large');\n    expect(radio2.size).to.equal('large');\n  });\n\n  it('should update the size of all radio buttons when size changes', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group size=\"small\">\n        <sl-radio-button id=\"radio-1\" value=\"1\"></sl-radio-button>\n        <sl-radio-button id=\"radio-2\" value=\"2\"></sl-radio-button>\n      </sl-radio-group>\n    `);\n    const [radio1, radio2] = radioGroup.querySelectorAll('sl-radio-button')!;\n\n    expect(radio1.size).to.equal('small');\n    expect(radio2.size).to.equal('small');\n\n    radioGroup.size = 'large';\n    await radioGroup.updateComplete;\n\n    expect(radio1.size).to.equal('large');\n    expect(radio2.size).to.equal('large');\n  });\n});\n\ndescribe('when handling focus', () => {\n  const doAction = async (instance: SlRadioGroup, type: string) => {\n    if (type === 'focus') {\n      instance.focus();\n      await instance.updateComplete;\n      return;\n    }\n\n    const label = instance.shadowRoot!.querySelector<HTMLLabelElement>('#label')!;\n    label.click();\n    await instance.updateComplete;\n  };\n\n  // Tests for focus and label actions with radio buttons\n  ['focus', 'label'].forEach(actionType => {\n    describe(`when using ${actionType}`, () => {\n      it('should do nothing if all elements are disabled', async () => {\n        const el = await fixture<SlRadioGroup>(html`\n          <sl-radio-group>\n            <sl-radio id=\"radio-0\" value=\"0\" disabled></sl-radio>\n            <sl-radio id=\"radio-1\" value=\"1\" disabled></sl-radio>\n            <sl-radio id=\"radio-2\" value=\"2\" disabled></sl-radio>\n            <sl-radio id=\"radio-3\" value=\"3\" disabled></sl-radio>\n          </sl-radio-group>\n        `);\n\n        const validFocusHandler = sinon.spy();\n\n        Array.from(el.querySelectorAll<SlRadio>('sl-radio')).forEach(radio =>\n          radio.addEventListener('sl-focus', validFocusHandler)\n        );\n\n        expect(validFocusHandler).to.not.have.been.called;\n        await doAction(el, actionType);\n        expect(validFocusHandler).to.not.have.been.called;\n      });\n\n      it('should focus the first radio that is enabled when the group receives focus', async () => {\n        const el = await fixture<SlRadioGroup>(html`\n          <sl-radio-group>\n            <sl-radio id=\"radio-0\" value=\"0\" disabled></sl-radio>\n            <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n            <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n            <sl-radio id=\"radio-3\" value=\"3\"></sl-radio>\n          </sl-radio-group>\n        `);\n\n        const invalidFocusHandler = sinon.spy();\n        const validFocusHandler = sinon.spy();\n\n        const disabledRadio = el.querySelector('#radio-0')!;\n        const validRadio = el.querySelector('#radio-1')!;\n\n        disabledRadio.addEventListener('sl-focus', invalidFocusHandler);\n        validRadio.addEventListener('sl-focus', validFocusHandler);\n\n        expect(invalidFocusHandler).to.not.have.been.called;\n        expect(validFocusHandler).to.not.have.been.called;\n\n        await doAction(el, actionType);\n\n        expect(invalidFocusHandler).to.not.have.been.called;\n        expect(validFocusHandler).to.have.been.called;\n      });\n\n      it('should focus the currently enabled radio when the group receives focus', async () => {\n        const el = await fixture<SlRadioGroup>(html`\n          <sl-radio-group value=\"2\">\n            <sl-radio id=\"radio-0\" value=\"0\" disabled></sl-radio>\n            <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n            <sl-radio id=\"radio-2\" value=\"2\" checked></sl-radio>\n            <sl-radio id=\"radio-3\" value=\"3\"></sl-radio>\n          </sl-radio-group>\n        `);\n\n        const invalidFocusHandler = sinon.spy();\n        const validFocusHandler = sinon.spy();\n\n        const disabledRadio = el.querySelector('#radio-0')!;\n        const validRadio = el.querySelector('#radio-2')!;\n\n        disabledRadio.addEventListener('sl-focus', invalidFocusHandler);\n        validRadio.addEventListener('sl-focus', validFocusHandler);\n\n        expect(invalidFocusHandler).to.not.have.been.called;\n        expect(validFocusHandler).to.not.have.been.called;\n\n        await doAction(el, actionType);\n\n        expect(invalidFocusHandler).to.not.have.been.called;\n        expect(validFocusHandler).to.have.been.called;\n      });\n    });\n  });\n});\n\ndescribe('when the value changes', () => {\n  it('should emit sl-change when toggled with the arrow keys', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group>\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n      </sl-radio-group>\n    `);\n    const firstRadio = radioGroup.querySelector<SlRadio>('#radio-1')!;\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    radioGroup.addEventListener('sl-change', changeHandler);\n    radioGroup.addEventListener('sl-input', inputHandler);\n    firstRadio.focus();\n    await sendKeys({ press: 'ArrowRight' });\n    await radioGroup.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(radioGroup.value).to.equal('2');\n  });\n\n  it('should emit sl-change and sl-input when clicked', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group>\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n      </sl-radio-group>\n    `);\n    const radio = radioGroup.querySelector<SlRadio>('#radio-1')!;\n    setTimeout(() => radio.click());\n    const event = (await oneEvent(radioGroup, 'sl-change')) as SlChangeEvent;\n    expect(event.target).to.equal(radioGroup);\n    expect(radioGroup.value).to.equal('1');\n  });\n\n  it('should emit sl-change and sl-input when toggled with spacebar', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group>\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n      </sl-radio-group>\n    `);\n    const radio = radioGroup.querySelector<SlRadio>('#radio-1')!;\n    radio.focus();\n    setTimeout(() => sendKeys({ press: ' ' }));\n    const event = (await oneEvent(radioGroup, 'sl-change')) as SlChangeEvent;\n    expect(event.target).to.equal(radioGroup);\n    expect(radioGroup.value).to.equal('1');\n  });\n\n  it('should not emit sl-change or sl-input when the value is changed programmatically', async () => {\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group value=\"1\">\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n        <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n      </sl-radio-group>\n    `);\n\n    radioGroup.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n    radioGroup.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n    radioGroup.value = '2';\n    await radioGroup.updateComplete;\n  });\n\n  it('should relatively position content to prevent visually hidden scroll bugs', async () => {\n    //\n    // See https://github.com/shoelace-style/shoelace/issues/1380\n    //\n    const radioGroup = await fixture<SlRadioGroup>(html`\n      <sl-radio-group value=\"1\">\n        <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n      </sl-radio-group>\n    `);\n\n    const formControl = radioGroup.shadowRoot!.querySelector('.form-control')!;\n    const visuallyHidden = radioGroup.shadowRoot!.querySelector('.visually-hidden')!;\n\n    expect(getComputedStyle(formControl).position).to.equal('relative');\n    expect(getComputedStyle(visuallyHidden).position).to.equal('absolute');\n  });\n\n  /**\n   * @see https://github.com/shoelace-style/shoelace/issues/1361\n   * This isn't really possible to test right now due to importing \"shoelace.js\" which\n   * auto-defines all of our components up front. This should be tested if we ever split\n   * to non-auto-defining components and not auto-defining for tests.\n   */\n  it.skip('should sync up radios and radio buttons if defined after radio group', async () => {\n    // customElements.define(\"sl-radio-group\", SlRadioGroup)\n    //\n    // const radioGroup = await fixture<SlRadioGroup>(html`\n    //   <sl-radio-group value=\"1\">\n    //     <sl-radio id=\"radio-1\" value=\"1\"></sl-radio>\n    //     <sl-radio id=\"radio-2\" value=\"2\"></sl-radio>\n    //   </sl-radio-group>\n    // `);\n    //\n    // await aTimeout(1)\n    //\n    // customElements.define(\"sl-radio-button\", SlRadioButton)\n    //\n    // expect(radioGroup.querySelector(\"sl-radio\")?.getAttribute(\"aria-checked\")).to.equal(\"false\")\n    //\n    // await aTimeout(1)\n    //\n    // customElements.define(\"sl-radio\", SlRadio)\n    //\n    // await aTimeout(1)\n    //\n    // expect(radioGroup.querySelector(\"sl-radio\")?.getAttribute(\"aria-checked\")).to.equal(\"true\")\n  });\n\n  runFormControlBaseTests('sl-radio-group');\n});\n"
  },
  {
    "path": "src/components/radio-group/radio-group.ts",
    "content": "import SlRadioGroup from './radio-group.component.js';\n\nexport * from './radio-group.component.js';\nexport default SlRadioGroup;\n\nSlRadioGroup.define('sl-radio-group');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-radio-group': SlRadioGroup;\n  }\n}\n"
  },
  {
    "path": "src/components/range/range.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { eventOptions, property, query, state } from 'lit/decorators.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './range.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Ranges allow the user to select a single value within a given range using a slider.\n * @documentation https://shoelace.style/components/range\n * @status stable\n * @since 2.0\n *\n * @slot label - The range's label. Alternatively, you can use the `label` attribute.\n * @slot help-text - Text that describes how to use the input. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-change - Emitted when an alteration to the control's value is committed by the user.\n * @event sl-focus - Emitted when the control gains focus.\n * @event sl-input - Emitted when the control receives input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart form-control - The form control that wraps the label, input, and help text.\n * @csspart form-control-label - The label's wrapper.\n * @csspart form-control-input - The range's wrapper.\n * @csspart form-control-help-text - The help text's wrapper.\n * @csspart base - The component's base wrapper.\n * @csspart input - The internal `<input>` element.\n * @csspart tooltip - The range's tooltip.\n *\n * @cssproperty --thumb-size - The size of the thumb.\n * @cssproperty --tooltip-offset - The vertical distance the tooltip is offset from the track.\n * @cssproperty --track-color-active - The color of the portion of the track that represents the current value.\n * @cssproperty --track-color-inactive - The of the portion of the track that represents the remaining value.\n * @cssproperty --track-height - The height of the track.\n * @cssproperty --track-active-offset - The point of origin of the active track.\n */\nexport default class SlRange extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n\n  private readonly formControlController = new FormControlController(this);\n  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');\n  private readonly localize = new LocalizeController(this);\n  private resizeObserver: ResizeObserver;\n\n  @query('.range__control') input: HTMLInputElement;\n  @query('.range__tooltip') output: HTMLOutputElement | null;\n\n  @state() private hasFocus = false;\n  @state() private hasTooltip = false;\n  @property() title = ''; // make reactive to pass through\n\n  /** The name of the range, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** The current value of the range, submitted as a name/value pair with form data. */\n  @property({ type: Number }) value = 0;\n\n  /** The range's label. If you need to display HTML, use the `label` slot instead. */\n  @property() label = '';\n\n  /** The range's help text. If you need to display HTML, use the help-text slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** Disables the range. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** The minimum acceptable value of the range. */\n  @property({ type: Number }) min = 0;\n\n  /** The maximum acceptable value of the range. */\n  @property({ type: Number }) max = 100;\n\n  /** The interval at which the range will increase and decrease. */\n  @property({ type: Number }) step = 1;\n\n  /** The preferred placement of the range's tooltip. */\n  @property() tooltip: 'top' | 'bottom' | 'none' = 'top';\n\n  /**\n   * A function used to format the tooltip's value. The range's value is passed as the first and only argument. The\n   * function should return a string to display in the tooltip.\n   */\n  @property({ attribute: false }) tooltipFormatter: (value: number) => string = (value: number) => value.toString();\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue() defaultValue = 0;\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.resizeObserver = new ResizeObserver(() => this.syncRange());\n\n    if (this.value < this.min) {\n      this.value = this.min;\n    }\n    if (this.value > this.max) {\n      this.value = this.max;\n    }\n\n    this.updateComplete.then(() => {\n      this.syncRange();\n      this.resizeObserver.observe(this.input);\n    });\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.resizeObserver?.unobserve(this.input);\n  }\n\n  private handleChange() {\n    this.emit('sl-change');\n  }\n\n  private handleInput() {\n    this.value = parseFloat(this.input.value);\n    this.emit('sl-input');\n    this.syncRange();\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.hasTooltip = false;\n    this.emit('sl-blur');\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.hasTooltip = true;\n    this.emit('sl-focus');\n  }\n\n  @eventOptions({ passive: true })\n  private handleThumbDragStart() {\n    this.hasTooltip = true;\n  }\n\n  private handleThumbDragEnd() {\n    this.hasTooltip = false;\n  }\n\n  private syncProgress(percent: number) {\n    this.input.style.setProperty('--percent', `${percent * 100}%`);\n  }\n\n  private syncTooltip(percent: number) {\n    if (this.output !== null) {\n      const inputWidth = this.input.offsetWidth;\n      const tooltipWidth = this.output.offsetWidth;\n      const thumbSize = getComputedStyle(this.input).getPropertyValue('--thumb-size');\n      const isRtl = this.localize.dir() === 'rtl';\n      const percentAsWidth = inputWidth * percent;\n\n      // The calculations are used to \"guess\" where the thumb is located. Since we're using the native range control\n      // under the hood, we don't have access to the thumb's true coordinates. These measurements can be a pixel or two\n      // off depending on the size of the control, thumb, and tooltip dimensions.\n      if (isRtl) {\n        const x = `${inputWidth - percentAsWidth}px + ${percent} * ${thumbSize}`;\n        this.output.style.translate = `calc((${x} - ${tooltipWidth / 2}px - ${thumbSize} / 2))`;\n      } else {\n        const x = `${percentAsWidth}px - ${percent} * ${thumbSize}`;\n        this.output.style.translate = `calc(${x} - ${tooltipWidth / 2}px + ${thumbSize} / 2)`;\n      }\n    }\n  }\n\n  @watch('value', { waitUntilFirstUpdate: true })\n  handleValueChange() {\n    this.formControlController.updateValidity();\n\n    // The value may have constraints, so we set the native control's value and sync it back to ensure it adhere's to\n    // min, max, and step properly\n    this.input.value = this.value.toString();\n    this.value = parseFloat(this.input.value);\n\n    this.syncRange();\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Disabled form controls are always valid\n    this.formControlController.setValidity(this.disabled);\n  }\n\n  @watch('hasTooltip', { waitUntilFirstUpdate: true })\n  syncRange() {\n    const percent = Math.max(0, (this.value - this.min) / (this.max - this.min));\n\n    this.syncProgress(percent);\n\n    if (this.tooltip !== 'none' && this.hasTooltip) {\n      // Ensure updates are drawn before we sync the tooltip\n      this.updateComplete.then(() => this.syncTooltip(percent));\n    }\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  /** Sets focus on the range. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the range. */\n  blur() {\n    this.input.blur();\n  }\n\n  /** Increments the value of the range by the value of the step attribute. */\n  stepUp() {\n    this.input.stepUp();\n    if (this.value !== Number(this.input.value)) {\n      this.value = Number(this.input.value);\n    }\n  }\n\n  /** Decrements the value of the range by the value of the step attribute. */\n  stepDown() {\n    this.input.stepDown();\n    if (this.value !== Number(this.input.value)) {\n      this.value = Number(this.input.value);\n    }\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.input.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const hasLabelSlot = this.hasSlotController.test('label');\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasLabel = this.label ? true : !!hasLabelSlot;\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n\n    // NOTE - always bind value after min/max, otherwise it will be clamped\n    return html`\n      <div\n        part=\"form-control\"\n        class=${classMap({\n          'form-control': true,\n          'form-control--medium': true, // range only has one size\n          'form-control--has-label': hasLabel,\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          part=\"form-control-label\"\n          class=\"form-control__label\"\n          for=\"input\"\n          aria-hidden=${hasLabel ? 'false' : 'true'}\n        >\n          <slot name=\"label\">${this.label}</slot>\n        </label>\n\n        <div part=\"form-control-input\" class=\"form-control-input\">\n          <div\n            part=\"base\"\n            class=${classMap({\n              range: true,\n              'range--disabled': this.disabled,\n              'range--focused': this.hasFocus,\n              'range--rtl': this.localize.dir() === 'rtl',\n              'range--tooltip-visible': this.hasTooltip,\n              'range--tooltip-top': this.tooltip === 'top',\n              'range--tooltip-bottom': this.tooltip === 'bottom'\n            })}\n            @mousedown=${this.handleThumbDragStart}\n            @mouseup=${this.handleThumbDragEnd}\n            @touchstart=${this.handleThumbDragStart}\n            @touchend=${this.handleThumbDragEnd}\n          >\n            <input\n              part=\"input\"\n              id=\"input\"\n              class=\"range__control\"\n              title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n              type=\"range\"\n              name=${ifDefined(this.name)}\n              ?disabled=${this.disabled}\n              min=${ifDefined(this.min)}\n              max=${ifDefined(this.max)}\n              step=${ifDefined(this.step)}\n              .value=${live(this.value.toString())}\n              aria-describedby=\"help-text\"\n              @change=${this.handleChange}\n              @focus=${this.handleFocus}\n              @input=${this.handleInput}\n              @invalid=${this.handleInvalid}\n              @blur=${this.handleBlur}\n            />\n            ${this.tooltip !== 'none' && !this.disabled\n              ? html`\n                  <output part=\"tooltip\" class=\"range__tooltip\">\n                    ${typeof this.tooltipFormatter === 'function' ? this.tooltipFormatter(this.value) : this.value}\n                  </output>\n                `\n              : ''}\n          </div>\n        </div>\n\n        <div\n          part=\"form-control-help-text\"\n          id=\"help-text\"\n          class=\"form-control__help-text\"\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/range/range.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --thumb-size: 20px;\n    --tooltip-offset: 10px;\n    --track-color-active: var(--sl-color-neutral-200);\n    --track-color-inactive: var(--sl-color-neutral-200);\n    --track-active-offset: 0%;\n    --track-height: 6px;\n\n    display: block;\n  }\n\n  .range {\n    position: relative;\n  }\n\n  .range__control {\n    --percent: 0%;\n    -webkit-appearance: none;\n    border-radius: 3px;\n    width: 100%;\n    height: var(--track-height);\n    background: transparent;\n    line-height: var(--sl-input-height-medium);\n    vertical-align: middle;\n    margin: 0;\n\n    background-image: linear-gradient(\n      to right,\n      var(--track-color-inactive) 0%,\n      var(--track-color-inactive) min(var(--percent), var(--track-active-offset)),\n      var(--track-color-active) min(var(--percent), var(--track-active-offset)),\n      var(--track-color-active) max(var(--percent), var(--track-active-offset)),\n      var(--track-color-inactive) max(var(--percent), var(--track-active-offset)),\n      var(--track-color-inactive) 100%\n    );\n  }\n\n  .range--rtl .range__control {\n    background-image: linear-gradient(\n      to left,\n      var(--track-color-inactive) 0%,\n      var(--track-color-inactive) min(var(--percent), var(--track-active-offset)),\n      var(--track-color-active) min(var(--percent), var(--track-active-offset)),\n      var(--track-color-active) max(var(--percent), var(--track-active-offset)),\n      var(--track-color-inactive) max(var(--percent), var(--track-active-offset)),\n      var(--track-color-inactive) 100%\n    );\n  }\n\n  /* Webkit */\n  .range__control::-webkit-slider-runnable-track {\n    width: 100%;\n    height: var(--track-height);\n    border-radius: 3px;\n    border: none;\n  }\n\n  .range__control::-webkit-slider-thumb {\n    border: none;\n    width: var(--thumb-size);\n    height: var(--thumb-size);\n    border-radius: 50%;\n    background-color: var(--sl-color-primary-600);\n    border: solid var(--sl-input-border-width) var(--sl-color-primary-600);\n    -webkit-appearance: none;\n    margin-top: calc(var(--thumb-size) / -2 + var(--track-height) / 2);\n    cursor: pointer;\n  }\n\n  .range__control:enabled::-webkit-slider-thumb:hover {\n    background-color: var(--sl-color-primary-500);\n    border-color: var(--sl-color-primary-500);\n  }\n\n  .range__control:enabled:focus-visible::-webkit-slider-thumb {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .range__control:enabled::-webkit-slider-thumb:active {\n    background-color: var(--sl-color-primary-500);\n    border-color: var(--sl-color-primary-500);\n    cursor: grabbing;\n  }\n\n  /* Firefox */\n  .range__control::-moz-focus-outer {\n    border: 0;\n  }\n\n  .range__control::-moz-range-progress {\n    background-color: var(--track-color-active);\n    border-radius: 3px;\n    height: var(--track-height);\n  }\n\n  .range__control::-moz-range-track {\n    width: 100%;\n    height: var(--track-height);\n    background-color: var(--track-color-inactive);\n    border-radius: 3px;\n    border: none;\n  }\n\n  .range__control::-moz-range-thumb {\n    border: none;\n    height: var(--thumb-size);\n    width: var(--thumb-size);\n    border-radius: 50%;\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n    transition:\n      var(--sl-transition-fast) border-color,\n      var(--sl-transition-fast) background-color,\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) box-shadow;\n    cursor: pointer;\n  }\n\n  .range__control:enabled::-moz-range-thumb:hover {\n    background-color: var(--sl-color-primary-500);\n    border-color: var(--sl-color-primary-500);\n  }\n\n  .range__control:enabled:focus-visible::-moz-range-thumb {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .range__control:enabled::-moz-range-thumb:active {\n    background-color: var(--sl-color-primary-500);\n    border-color: var(--sl-color-primary-500);\n    cursor: grabbing;\n  }\n\n  /* States */\n  .range__control:focus-visible {\n    outline: none;\n  }\n\n  .range__control:disabled {\n    opacity: 0.5;\n  }\n\n  .range__control:disabled::-webkit-slider-thumb {\n    cursor: not-allowed;\n  }\n\n  .range__control:disabled::-moz-range-thumb {\n    cursor: not-allowed;\n  }\n\n  /* Tooltip output */\n  .range__tooltip {\n    position: absolute;\n    z-index: var(--sl-z-index-tooltip);\n    left: 0;\n    border-radius: var(--sl-tooltip-border-radius);\n    background-color: var(--sl-tooltip-background-color);\n    font-family: var(--sl-tooltip-font-family);\n    font-size: var(--sl-tooltip-font-size);\n    font-weight: var(--sl-tooltip-font-weight);\n    line-height: var(--sl-tooltip-line-height);\n    color: var(--sl-tooltip-color);\n    opacity: 0;\n    padding: var(--sl-tooltip-padding);\n    transition: var(--sl-transition-fast) opacity;\n    pointer-events: none;\n  }\n\n  .range__tooltip:after {\n    content: '';\n    position: absolute;\n    width: 0;\n    height: 0;\n    left: 50%;\n    translate: calc(-1 * var(--sl-tooltip-arrow-size));\n  }\n\n  .range--tooltip-visible .range__tooltip {\n    opacity: 1;\n  }\n\n  /* Tooltip on top */\n  .range--tooltip-top .range__tooltip {\n    top: calc(-1 * var(--thumb-size) - var(--tooltip-offset));\n  }\n\n  .range--tooltip-top .range__tooltip:after {\n    border-top: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);\n    border-left: var(--sl-tooltip-arrow-size) solid transparent;\n    border-right: var(--sl-tooltip-arrow-size) solid transparent;\n    top: 100%;\n  }\n\n  /* Tooltip on bottom */\n  .range--tooltip-bottom .range__tooltip {\n    bottom: calc(-1 * var(--thumb-size) - var(--tooltip-offset));\n  }\n\n  .range--tooltip-bottom .range__tooltip:after {\n    border-bottom: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);\n    border-left: var(--sl-tooltip-arrow-size) solid transparent;\n    border-right: var(--sl-tooltip-arrow-size) solid transparent;\n    bottom: 100%;\n  }\n\n  @media (forced-colors: active) {\n    .range__control,\n    .range__tooltip {\n      border: solid 1px transparent;\n    }\n\n    .range__control::-webkit-slider-thumb {\n      border: solid 1px transparent;\n    }\n\n    .range__control::-moz-range-thumb {\n      border: solid 1px transparent;\n    }\n\n    .range__tooltip:after {\n      display: none;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/range/range.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { clickOnElement } from '../../internal/test.js';\nimport { expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport { serialize } from '../../utilities/form.js';\nimport sinon from 'sinon';\nimport type SlRange from './range.js';\n\ndescribe('<sl-range>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlRange>(html` <sl-range label=\"Name\"></sl-range> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlRange>(html` <sl-range></sl-range> `);\n\n    expect(el.name).to.equal('');\n    expect(el.value).to.equal(0);\n    expect(el.title).to.equal('');\n    expect(el.label).to.equal('');\n    expect(el.helpText).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.checkValidity()).to.be.true;\n    expect(el.min).to.equal(0);\n    expect(el.max).to.equal(100);\n    expect(el.step).to.equal(1);\n    expect(el.tooltip).to.equal('top');\n    expect(el.defaultValue).to.equal(0);\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlRange>(html` <sl-range title=\"Test\"></sl-range> `);\n    const input = el.shadowRoot!.querySelector('input')!;\n\n    expect(input.title).to.equal('Test');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlRange>(html` <sl-range disabled></sl-range> `);\n    const input = el.shadowRoot!.querySelector<HTMLInputElement>('[part~=\"input\"]')!;\n\n    expect(input.disabled).to.be.true;\n  });\n\n  describe('when the value changes', () => {\n    it('should emit sl-change and sl-input when the value changes from clicking the slider', async () => {\n      const el = await fixture<SlRange>(html` <sl-range value=\"0\"></sl-range> `);\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n      await clickOnElement(el, 'right');\n      await el.updateComplete;\n\n      expect(el.value).to.equal(100);\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input and decrease the value when pressing left arrow', async () => {\n      const el = await fixture<SlRange>(html` <sl-range value=\"50\"></sl-range> `);\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n      el.focus();\n      await sendKeys({ press: 'ArrowLeft' });\n      await el.updateComplete;\n\n      expect(el.value).to.equal(49);\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should emit sl-change and sl-input and decrease the value when pressing right arrow', async () => {\n      const el = await fixture<SlRange>(html` <sl-range value=\"50\"></sl-range> `);\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n      el.focus();\n      await sendKeys({ press: 'ArrowRight' });\n      await el.updateComplete;\n\n      expect(el.value).to.equal(51);\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n    });\n\n    it('should not emit sl-change or sl-input when changing the value programmatically', async () => {\n      const el = await fixture<SlRange>(html` <sl-range value=\"0\"></sl-range> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.value = 50;\n\n      await el.updateComplete;\n    });\n\n    it('should not emit sl-change or sl-input when stepUp() is called programmatically', async () => {\n      const el = await fixture<SlRange>(html` <sl-range step=\"2\" value=\"2\"></sl-range> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.stepUp();\n      await el.updateComplete;\n    });\n\n    it('should not emit sl-change or sl-input when stepDown() is called programmatically', async () => {\n      const el = await fixture<SlRange>(html` <sl-range step=\"2\" value=\"2\"></sl-range> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.stepDown();\n      await el.updateComplete;\n    });\n  });\n\n  describe('step', () => {\n    it('should increment by step when stepUp() is called', async () => {\n      const el = await fixture<SlRange>(html` <sl-range step=\"2\" value=\"2\"></sl-range> `);\n\n      el.stepUp();\n      await el.updateComplete;\n      expect(el.value).to.equal(4);\n    });\n\n    it('should decrement by step when stepDown() is called', async () => {\n      const el = await fixture<SlRange>(html` <sl-range step=\"2\" value=\"2\"></sl-range> `);\n\n      el.stepDown();\n      await el.updateComplete;\n      expect(el.value).to.equal(0);\n    });\n  });\n\n  describe('when submitting a form', () => {\n    it('should serialize its name and value with FormData', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-range name=\"a\" value=\"1\"></sl-range></form> `);\n      const formData = new FormData(form);\n      expect(formData.get('a')).to.equal('1');\n    });\n\n    it('should serialize its name and value with JSON', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-range name=\"a\" value=\"1\"></sl-range></form> `);\n      const json = serialize(form);\n      expect(json.a).to.equal('1');\n    });\n\n    it('should be invalid when setCustomValidity() is called with a non-empty value', async () => {\n      const range = await fixture<HTMLFormElement>(html` <sl-range></sl-range> `);\n\n      range.setCustomValidity('Invalid selection');\n      await range.updateComplete;\n\n      expect(range.checkValidity()).to.be.false;\n      expect(range.hasAttribute('data-invalid')).to.be.true;\n      expect(range.hasAttribute('data-valid')).to.be.false;\n      expect(range.hasAttribute('data-user-invalid')).to.be.false;\n      expect(range.hasAttribute('data-user-valid')).to.be.false;\n\n      await clickOnElement(range);\n      await range.updateComplete;\n      range.blur();\n      await range.updateComplete;\n\n      expect(range.hasAttribute('data-user-invalid')).to.be.true;\n      expect(range.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-range></sl-range></form> `);\n      const range = el.querySelector<SlRange>('sl-range')!;\n\n      range.setCustomValidity('Invalid value');\n      await range.updateComplete;\n\n      expect(range.hasAttribute('data-invalid')).to.be.true;\n      expect(range.hasAttribute('data-valid')).to.be.false;\n      expect(range.hasAttribute('data-user-invalid')).to.be.false;\n      expect(range.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-range form=\"f\" name=\"a\" value=\"50\"></sl-range>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('50');\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-range name=\"a\" value=\"99\"></sl-range>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const input = form.querySelector('sl-range')!;\n      input.value = 80;\n\n      await input.updateComplete;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await input.updateComplete;\n\n      expect(input.value).to.equal(99);\n\n      input.defaultValue = 0;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await input.updateComplete;\n\n      expect(input.value).to.equal(0);\n    });\n  });\n\n  runFormControlBaseTests('sl-range');\n});\n"
  },
  {
    "path": "src/components/range/range.ts",
    "content": "import SlRange from './range.component.js';\n\nexport * from './range.component.js';\nexport default SlRange;\n\nSlRange.define('sl-range');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-range': SlRange;\n  }\n}\n"
  },
  {
    "path": "src/components/rating/rating.component.ts",
    "content": "import { clamp } from '../../internal/math.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { eventOptions, property, query, state } from 'lit/decorators.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport styles from './rating.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Ratings give users a way to quickly view and provide feedback.\n * @documentation https://shoelace.style/components/rating\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n *\n * @event sl-change - Emitted when the rating's value changes.\n * @event {{ phase: 'start' | 'move' | 'end', value: number }} sl-hover - Emitted when the user hovers over a value. The\n *  `phase` property indicates when hovering starts, moves to a new value, or ends. The `value` property tells what the\n *  rating's value would be if the user were to commit to the hovered value.\n *\n * @csspart base - The component's base wrapper.\n *\n * @cssproperty --symbol-color - The inactive color for symbols.\n * @cssproperty --symbol-color-active - The active color for symbols.\n * @cssproperty --symbol-size - The size of symbols.\n * @cssproperty --symbol-spacing - The spacing to use around symbols.\n */\nexport default class SlRating extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon': SlIcon };\n\n  private readonly localize = new LocalizeController(this);\n\n  @query('.rating') rating: HTMLElement;\n\n  @state() private hoverValue = 0;\n  @state() private isHovering = false;\n\n  /** A label that describes the rating to assistive devices. */\n  @property() label = '';\n\n  /** The current rating. */\n  @property({ type: Number }) value = 0;\n\n  /** The highest rating to show. */\n  @property({ type: Number }) max = 5;\n\n  /**\n   * The precision at which the rating will increase and decrease. For example, to allow half-star ratings, set this\n   * attribute to `0.5`.\n   */\n  @property({ type: Number }) precision = 1;\n\n  /** Makes the rating readonly. */\n  @property({ type: Boolean, reflect: true }) readonly = false;\n\n  /** Disables the rating. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * A function that customizes the symbol to be rendered. The first and only argument is the rating's current value.\n   * The function should return a string containing trusted HTML of the symbol to render at the specified value. Works\n   * well with `<sl-icon>` elements.\n   */\n  @property() getSymbol: (value: number) => string = () => '<sl-icon name=\"star-fill\" library=\"system\"></sl-icon>';\n\n  private getValueFromMousePosition(event: MouseEvent) {\n    return this.getValueFromXCoordinate(event.clientX);\n  }\n\n  private getValueFromTouchPosition(event: TouchEvent) {\n    return this.getValueFromXCoordinate(event.touches[0].clientX);\n  }\n\n  private getValueFromXCoordinate(coordinate: number) {\n    const isRtl = this.localize.dir() === 'rtl';\n    const { left, right, width } = this.rating.getBoundingClientRect();\n    const value = isRtl\n      ? this.roundToPrecision(((right - coordinate) / width) * this.max, this.precision)\n      : this.roundToPrecision(((coordinate - left) / width) * this.max, this.precision);\n\n    return clamp(value, 0, this.max);\n  }\n\n  private handleClick(event: MouseEvent) {\n    if (this.disabled) {\n      return;\n    }\n\n    this.setValue(this.getValueFromMousePosition(event));\n    this.emit('sl-change');\n  }\n\n  private setValue(newValue: number) {\n    if (this.disabled || this.readonly) {\n      return;\n    }\n\n    this.value = newValue === this.value ? 0 : newValue;\n    this.isHovering = false;\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    const isLtr = this.localize.dir() === 'ltr';\n    const isRtl = this.localize.dir() === 'rtl';\n    const oldValue = this.value;\n\n    if (this.disabled || this.readonly) {\n      return;\n    }\n\n    if (event.key === 'ArrowDown' || (isLtr && event.key === 'ArrowLeft') || (isRtl && event.key === 'ArrowRight')) {\n      const decrement = event.shiftKey ? 1 : this.precision;\n      this.value = Math.max(0, this.value - decrement);\n      event.preventDefault();\n    }\n\n    if (event.key === 'ArrowUp' || (isLtr && event.key === 'ArrowRight') || (isRtl && event.key === 'ArrowLeft')) {\n      const increment = event.shiftKey ? 1 : this.precision;\n      this.value = Math.min(this.max, this.value + increment);\n      event.preventDefault();\n    }\n\n    if (event.key === 'Home') {\n      this.value = 0;\n      event.preventDefault();\n    }\n\n    if (event.key === 'End') {\n      this.value = this.max;\n      event.preventDefault();\n    }\n\n    if (this.value !== oldValue) {\n      this.emit('sl-change');\n    }\n  }\n\n  private handleMouseEnter(event: MouseEvent) {\n    this.isHovering = true;\n    this.hoverValue = this.getValueFromMousePosition(event);\n  }\n\n  private handleMouseMove(event: MouseEvent) {\n    this.hoverValue = this.getValueFromMousePosition(event);\n  }\n\n  private handleMouseLeave() {\n    this.isHovering = false;\n  }\n\n  private handleTouchStart(event: TouchEvent) {\n    this.isHovering = true;\n    this.hoverValue = this.getValueFromTouchPosition(event);\n\n    // Prevent scrolling when touch is initiated\n    event.preventDefault();\n  }\n\n  @eventOptions({ passive: true })\n  private handleTouchMove(event: TouchEvent) {\n    this.hoverValue = this.getValueFromTouchPosition(event);\n  }\n\n  private handleTouchEnd(event: TouchEvent) {\n    this.isHovering = false;\n    this.setValue(this.hoverValue);\n    this.emit('sl-change');\n\n    // Prevent click on mobile devices\n    event.preventDefault();\n  }\n\n  private roundToPrecision(numberToRound: number, precision = 0.5) {\n    const multiplier = 1 / precision;\n    return Math.ceil(numberToRound * multiplier) / multiplier;\n  }\n\n  @watch('hoverValue')\n  handleHoverValueChange() {\n    this.emit('sl-hover', {\n      detail: {\n        phase: 'move',\n        value: this.hoverValue\n      }\n    });\n  }\n\n  @watch('isHovering')\n  handleIsHoveringChange() {\n    this.emit('sl-hover', {\n      detail: {\n        phase: this.isHovering ? 'start' : 'end',\n        value: this.hoverValue\n      }\n    });\n  }\n\n  /** Sets focus on the rating. */\n  focus(options?: FocusOptions) {\n    this.rating.focus(options);\n  }\n\n  /** Removes focus from the rating. */\n  blur() {\n    this.rating.blur();\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n    const counter = Array.from(Array(this.max).keys());\n    let displayValue = 0;\n\n    if (this.disabled || this.readonly) {\n      displayValue = this.value;\n    } else {\n      displayValue = this.isHovering ? this.hoverValue : this.value;\n    }\n\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          rating: true,\n          'rating--readonly': this.readonly,\n          'rating--disabled': this.disabled,\n          'rating--rtl': isRtl\n        })}\n        role=\"slider\"\n        aria-label=${this.label}\n        aria-disabled=${this.disabled ? 'true' : 'false'}\n        aria-readonly=${this.readonly ? 'true' : 'false'}\n        aria-valuenow=${this.value}\n        aria-valuemin=${0}\n        aria-valuemax=${this.max}\n        tabindex=${this.disabled || this.readonly ? '-1' : '0'}\n        @click=${this.handleClick}\n        @keydown=${this.handleKeyDown}\n        @mouseenter=${this.handleMouseEnter}\n        @touchstart=${this.handleTouchStart}\n        @mouseleave=${this.handleMouseLeave}\n        @touchend=${this.handleTouchEnd}\n        @mousemove=${this.handleMouseMove}\n        @touchmove=${this.handleTouchMove}\n      >\n        <span class=\"rating__symbols\">\n          ${counter.map(index => {\n            if (displayValue > index && displayValue < index + 1) {\n              // Users can click the current value to clear the rating. When this happens, we set this.isHovering to\n              // false to prevent the hover state from confusing them as they move the mouse out of the control. This\n              // extra mouseenter will reinstate it if they happen to mouse over an adjacent symbol.\n              return html`\n                <span\n                  class=${classMap({\n                    rating__symbol: true,\n                    'rating__partial-symbol-container': true,\n                    'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1\n                  })}\n                  role=\"presentation\"\n                >\n                  <div\n                    style=${styleMap({\n                      clipPath: isRtl\n                        ? `inset(0 ${(displayValue - index) * 100}% 0 0)`\n                        : `inset(0 0 0 ${(displayValue - index) * 100}%)`\n                    })}\n                  >\n                    ${unsafeHTML(this.getSymbol(index + 1))}\n                  </div>\n                  <div\n                    class=\"rating__partial--filled\"\n                    style=${styleMap({\n                      clipPath: isRtl\n                        ? `inset(0 0 0 ${100 - (displayValue - index) * 100}%)`\n                        : `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`\n                    })}\n                  >\n                    ${unsafeHTML(this.getSymbol(index + 1))}\n                  </div>\n                </span>\n              `;\n            }\n\n            return html`\n              <span\n                class=${classMap({\n                  rating__symbol: true,\n                  'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1,\n                  'rating__symbol--active': displayValue >= index + 1\n                })}\n                role=\"presentation\"\n              >\n                ${unsafeHTML(this.getSymbol(index + 1))}\n              </span>\n            `;\n          })}\n        </span>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/rating/rating.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --symbol-color: var(--sl-color-neutral-300);\n    --symbol-color-active: var(--sl-color-amber-500);\n    --symbol-size: 1.2rem;\n    --symbol-spacing: var(--sl-spacing-3x-small);\n\n    display: inline-flex;\n  }\n\n  .rating {\n    position: relative;\n    display: inline-flex;\n    border-radius: var(--sl-border-radius-medium);\n    vertical-align: middle;\n  }\n\n  .rating:focus {\n    outline: none;\n  }\n\n  .rating:focus-visible {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .rating__symbols {\n    display: inline-flex;\n    position: relative;\n    font-size: var(--symbol-size);\n    line-height: 0;\n    color: var(--symbol-color);\n    white-space: nowrap;\n    cursor: pointer;\n  }\n\n  .rating__symbols > * {\n    padding: var(--symbol-spacing);\n  }\n\n  .rating__symbol--active,\n  .rating__partial--filled {\n    color: var(--symbol-color-active);\n  }\n\n  .rating__partial-symbol-container {\n    position: relative;\n  }\n\n  .rating__partial--filled {\n    position: absolute;\n    top: var(--symbol-spacing);\n    left: var(--symbol-spacing);\n  }\n\n  .rating__symbol {\n    transition: var(--sl-transition-fast) scale;\n    pointer-events: none;\n  }\n\n  .rating__symbol--hover {\n    scale: 1.2;\n  }\n\n  .rating--disabled .rating__symbols,\n  .rating--readonly .rating__symbols {\n    cursor: default;\n  }\n\n  .rating--disabled .rating__symbol--hover,\n  .rating--readonly .rating__symbol--hover {\n    scale: none;\n  }\n\n  .rating--disabled {\n    opacity: 0.5;\n  }\n\n  .rating--disabled .rating__symbols {\n    cursor: not-allowed;\n  }\n\n  /* Forced colors mode */\n  @media (forced-colors: active) {\n    .rating__symbol--active {\n      color: SelectedItem;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/rating/rating.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { clickOnElement } from '../../internal/test.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlRating from './rating.js';\n\ndescribe('<sl-rating>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\"></sl-rating> `);\n    await expect(el).to.be.accessible();\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('role')).to.equal('slider');\n    expect(base.getAttribute('aria-disabled')).to.equal('false');\n    expect(base.getAttribute('aria-readonly')).to.equal('false');\n    expect(base.getAttribute('aria-valuenow')).to.equal('0');\n    expect(base.getAttribute('aria-valuemin')).to.equal('0');\n    expect(base.getAttribute('aria-valuemax')).to.equal('5');\n    expect(base.getAttribute('tabindex')).to.equal('0');\n    expect(base.getAttribute('class')).to.equal(' rating ');\n  });\n\n  it('should be readonly with the readonly attribute', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\" readonly></sl-rating> `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('aria-readonly')).to.equal('true');\n    expect(base.getAttribute('class')).to.equal(' rating rating--readonly ');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\" disabled></sl-rating> `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('aria-disabled')).to.equal('true');\n    expect(base.getAttribute('class')).to.equal(' rating rating--disabled ');\n  });\n\n  it('should set max value by attribute', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\" max=\"12\"></sl-rating> `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('aria-valuemax')).to.equal('12');\n  });\n\n  it('should set selected value by attribute', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\" value=\"3\"></sl-rating> `);\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('aria-valuenow')).to.equal('3');\n  });\n\n  it('should emit sl-change when clicked', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating></sl-rating> `);\n    const lastSymbol = el.shadowRoot!.querySelector<HTMLSpanElement>('.rating__symbol:last-child')!;\n    const changeHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n\n    await clickOnElement(lastSymbol);\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(el.value).to.equal(5);\n  });\n\n  it('should emit sl-change when the value is changed with the keyboard', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating></sl-rating> `);\n    const changeHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.focus();\n    await el.updateComplete;\n    await sendKeys({ press: 'ArrowRight' });\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(el.value).to.equal(1);\n  });\n\n  it('should not emit sl-change when disabled', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating value=\"5\" disabled></sl-rating> `);\n    const lastSymbol = el.shadowRoot!.querySelector<HTMLSpanElement>('.rating__symbol:last-child')!;\n    const changeHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n\n    await clickOnElement(lastSymbol);\n    await el.updateComplete;\n\n    expect(changeHandler).to.not.have.been.called;\n    expect(el.value).to.equal(5);\n  });\n\n  it('should not emit sl-change when the value is changed programmatically', async () => {\n    const el = await fixture<SlRating>(html` <sl-rating label=\"Test\" value=\"1\"></sl-rating> `);\n    el.addEventListener('sl-change', () => expect.fail('sl-change incorrectly emitted'));\n    el.value = 5;\n    await el.updateComplete;\n  });\n\n  describe('focus', () => {\n    it('should focus inner div', async () => {\n      const el = await fixture<SlRating>(html` <sl-rating label=\"Test\"></sl-rating> `);\n\n      const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n      el.focus();\n      await el.updateComplete;\n\n      expect(el.shadowRoot!.activeElement).to.equal(base);\n    });\n  });\n\n  describe('blur', () => {\n    it('should blur inner div', async () => {\n      const el = await fixture<SlRating>(html` <sl-rating label=\"Test\"></sl-rating> `);\n\n      el.focus();\n      await el.updateComplete;\n\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.shadowRoot!.activeElement).to.equal(null);\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/rating/rating.ts",
    "content": "import SlRating from './rating.component.js';\n\nexport * from './rating.component.js';\nexport default SlRating;\n\nSlRating.define('sl-rating');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-rating': SlRating;\n  }\n}\n"
  },
  {
    "path": "src/components/relative-time/relative-time.component.ts",
    "content": "import { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, state } from 'lit/decorators.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\n\ninterface UnitConfig {\n  max: number;\n  value: number;\n  unit: Intl.RelativeTimeFormatUnit;\n}\n\nconst availableUnits: UnitConfig[] = [\n  { max: 2760000, value: 60000, unit: 'minute' }, // max 46 minutes\n  { max: 72000000, value: 3600000, unit: 'hour' }, // max 20 hours\n  { max: 518400000, value: 86400000, unit: 'day' }, // max 6 days\n  { max: 2419200000, value: 604800000, unit: 'week' }, // max 28 days\n  { max: 28512000000, value: 2592000000, unit: 'month' }, // max 11 months\n  { max: Infinity, value: 31536000000, unit: 'year' }\n];\n\n/**\n * @summary Outputs a localized time phrase relative to the current date and time.\n * @documentation https://shoelace.style/components/relative-time\n * @status stable\n * @since 2.0\n */\nexport default class SlRelativeTime extends ShoelaceElement {\n  private readonly localize = new LocalizeController(this);\n  private updateTimeout: number;\n\n  @state() private isoTime = '';\n  @state() private relativeTime = '';\n\n  /**\n   * The date from which to calculate time from. If not set, the current date and time will be used. When passing a\n   * string, it's strongly recommended to use the ISO 8601 format to ensure timezones are handled correctly. To convert\n   * a date to this format in JavaScript, use [`date.toISOString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString).\n   */\n  @property() date: Date | string = new Date();\n\n  /** The formatting style to use. */\n  @property() format: 'long' | 'short' | 'narrow' = 'long';\n\n  /**\n   * When `auto`, values such as \"yesterday\" and \"tomorrow\" will be shown when possible. When `always`, values such as\n   * \"1 day ago\" and \"in 1 day\" will be shown.\n   */\n  @property() numeric: 'always' | 'auto' = 'auto';\n\n  /** Keep the displayed value up to date as time passes. */\n  @property({ type: Boolean }) sync = false;\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    clearTimeout(this.updateTimeout);\n  }\n\n  render() {\n    const now = new Date();\n    const then = new Date(this.date);\n\n    // Check for an invalid date\n    if (isNaN(then.getMilliseconds())) {\n      this.relativeTime = '';\n      this.isoTime = '';\n      return '';\n    }\n\n    const diff = then.getTime() - now.getTime();\n    const { unit, value } = availableUnits.find(singleUnit => Math.abs(diff) < singleUnit.max)!;\n\n    this.isoTime = then.toISOString();\n\n    this.relativeTime = this.localize.relativeTime(Math.round(diff / value), unit, {\n      numeric: this.numeric,\n      style: this.format\n    });\n\n    // If sync is enabled, update as time passes\n    clearTimeout(this.updateTimeout);\n\n    if (this.sync) {\n      let nextInterval: number;\n\n      // NOTE: this could be optimized to determine when the next update should actually occur, but the size and cost of\n      // that logic probably isn't worth the performance benefit\n      if (unit === 'minute') {\n        nextInterval = getTimeUntilNextUnit('second');\n      } else if (unit === 'hour') {\n        nextInterval = getTimeUntilNextUnit('minute');\n      } else if (unit === 'day') {\n        nextInterval = getTimeUntilNextUnit('hour');\n      } else {\n        // Cap updates at once per day. It's unlikely a user will reach this value, plus setTimeout has a limit on the\n        // value it can accept. https://stackoverflow.com/a/3468650/567486\n        nextInterval = getTimeUntilNextUnit('day'); // next day\n      }\n\n      this.updateTimeout = window.setTimeout(() => this.requestUpdate(), nextInterval);\n    }\n\n    return html` <time datetime=${this.isoTime}>${this.relativeTime}</time> `;\n  }\n}\n\n// Calculates the number of milliseconds until the next respective unit changes. This ensures that all components\n// update at the same time which is less distracting than updating independently.\nfunction getTimeUntilNextUnit(unit: 'second' | 'minute' | 'hour' | 'day') {\n  const units = { second: 1000, minute: 60000, hour: 3600000, day: 86400000 };\n  const value = units[unit];\n  return value - (Date.now() % value);\n}\n"
  },
  {
    "path": "src/components/relative-time/relative-time.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlRelativeTime from './relative-time.js';\n\ninterface SlRelativeTimeTestCase {\n  date: Date;\n  expectedOutput: string;\n}\n\nconst extractTimeElement = (relativeTime: SlRelativeTime): HTMLTimeElement | null => {\n  return relativeTime.shadowRoot?.querySelector('time') || null;\n};\n\nconst expectFormattedRelativeTimeToBe = async (relativeTime: SlRelativeTime, expectedOutput: string): Promise<void> => {\n  await relativeTime.updateComplete;\n  const textContent = extractTimeElement(relativeTime)?.textContent;\n  expect(textContent).to.equal(expectedOutput);\n};\n\nconst createRelativeTimeWithDate = async (relativeDate: Date): Promise<SlRelativeTime> => {\n  const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n    <sl-relative-time lang=\"en-US\"></sl-relative-time>\n  `);\n  relativeTime.date = relativeDate;\n  return relativeTime;\n};\n\nconst minuteInSeconds = 60_000;\nconst hourInSeconds = minuteInSeconds * 60;\nconst dayInSeconds = hourInSeconds * 24;\nconst weekInSeconds = dayInSeconds * 7;\nconst monthInSeconds = dayInSeconds * 30;\nconst nonLeapYearInSeconds = dayInSeconds * 356;\n\nconst currentTime = new Date('2022-10-30T15:22:10.100Z');\nconst yesterday = new Date(currentTime.getTime() - dayInSeconds);\nconst testCases: SlRelativeTimeTestCase[] = [\n  {\n    date: new Date(currentTime.getTime() - minuteInSeconds),\n    expectedOutput: '1 minute ago'\n  },\n  {\n    date: new Date(currentTime.getTime() - hourInSeconds),\n    expectedOutput: '1 hour ago'\n  },\n  {\n    date: yesterday,\n    expectedOutput: 'yesterday'\n  },\n  {\n    date: new Date(currentTime.getTime() - 4 * dayInSeconds),\n    expectedOutput: '4 days ago'\n  },\n  {\n    date: new Date(currentTime.getTime() - weekInSeconds),\n    expectedOutput: 'last week'\n  },\n  {\n    date: new Date(currentTime.getTime() - monthInSeconds),\n    expectedOutput: 'last month'\n  },\n  {\n    date: new Date(currentTime.getTime() - nonLeapYearInSeconds),\n    expectedOutput: 'last year'\n  },\n  {\n    date: new Date(currentTime.getTime() + minuteInSeconds),\n    expectedOutput: 'in 1 minute'\n  }\n];\n\ndescribe('sl-relative-time', () => {\n  it('should pass accessibility tests', async () => {\n    const relativeTime = await createRelativeTimeWithDate(currentTime);\n\n    await expect(relativeTime).to.be.accessible();\n  });\n\n  describe('handles time correctly', () => {\n    let clock: sinon.SinonFakeTimers | null = null;\n\n    beforeEach(() => {\n      clock = sinon.useFakeTimers(currentTime);\n    });\n\n    afterEach(() => {\n      clock?.restore();\n    });\n\n    testCases.forEach(testCase => {\n      it(`shows the correct relative time given a Date object: ${testCase.expectedOutput}`, async () => {\n        const relativeTime = await createRelativeTimeWithDate(testCase.date);\n\n        await expectFormattedRelativeTimeToBe(relativeTime, testCase.expectedOutput);\n      });\n\n      it(`shows the correct relative time given a String object: ${testCase.expectedOutput}`, async () => {\n        const dateString = testCase.date.toISOString();\n\n        const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n          <sl-relative-time lang=\"en-US\" date=\"${dateString}\"></sl-relative-time>\n        `);\n\n        await expectFormattedRelativeTimeToBe(relativeTime, testCase.expectedOutput);\n      });\n    });\n\n    it('always shows numeric if requested via numeric property', async () => {\n      const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n        <sl-relative-time lang=\"en-US\" numeric=\"always\"></sl-relative-time>\n      `);\n      relativeTime.date = yesterday;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, '1 day ago');\n    });\n\n    it('shows human readable form if appropriate and numeric property is auto', async () => {\n      const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n        <sl-relative-time lang=\"en-US\" numeric=\"auto\"></sl-relative-time>\n      `);\n      relativeTime.date = yesterday;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, 'yesterday');\n    });\n\n    it('shows the set date with the proper attributes at the time object', async () => {\n      const relativeTime = await createRelativeTimeWithDate(yesterday);\n\n      await relativeTime.updateComplete;\n      const timeElement = extractTimeElement(relativeTime);\n      expect(timeElement?.dateTime).to.equal(yesterday.toISOString());\n    });\n\n    it('allows to use a short form of the unit', async () => {\n      const twoYearsAgo = new Date(currentTime.getTime() - 2 * nonLeapYearInSeconds);\n      const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n        <sl-relative-time lang=\"en-US\" numeric=\"always\" format=\"short\"></sl-relative-time>\n      `);\n      relativeTime.date = twoYearsAgo;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, '2 yr. ago');\n    });\n\n    it('allows to use a long form of the unit', async () => {\n      const twoYearsAgo = new Date(currentTime.getTime() - 2 * nonLeapYearInSeconds);\n      const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n        <sl-relative-time lang=\"en-US\" numeric=\"always\" format=\"long\"></sl-relative-time>\n      `);\n      relativeTime.date = twoYearsAgo;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, '2 years ago');\n    });\n\n    it('is formatted according to the requested locale', async () => {\n      const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n        <sl-relative-time lang=\"de-DE\" numeric=\"auto\"></sl-relative-time>\n      `);\n      relativeTime.date = yesterday;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, 'gestern');\n    });\n\n    it('keeps the component in sync if requested', async () => {\n      const relativeTime = await createRelativeTimeWithDate(yesterday);\n      relativeTime.sync = true;\n\n      await expectFormattedRelativeTimeToBe(relativeTime, 'yesterday');\n\n      clock?.tick(dayInSeconds);\n\n      await expectFormattedRelativeTimeToBe(relativeTime, '2 days ago');\n    });\n  });\n\n  it('does not display a time element on invalid time string', async () => {\n    const invalidDateString = 'thisIsNotATimeString';\n\n    const relativeTime: SlRelativeTime = await fixture<SlRelativeTime>(html`\n      <sl-relative-time lang=\"en-US\" date=\"${invalidDateString}\"></sl-relative-time>\n    `);\n\n    await relativeTime.updateComplete;\n    expect(extractTimeElement(relativeTime)).to.be.null;\n  });\n});\n"
  },
  {
    "path": "src/components/relative-time/relative-time.ts",
    "content": "import SlRelativeTime from './relative-time.component.js';\n\nexport * from './relative-time.component.js';\nexport default SlRelativeTime;\n\nSlRelativeTime.define('sl-relative-time');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-relative-time': SlRelativeTime;\n  }\n}\n"
  },
  {
    "path": "src/components/resize-observer/resize-observer.component.ts",
    "content": "import { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './resize-observer.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary The Resize Observer component offers a thin, declarative interface to the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).\n * @documentation https://shoelace.style/components/resize-observer\n * @status stable\n * @since 2.0\n *\n * @slot - One or more elements to watch for resizing.\n *\n * @event {{ entries: ResizeObserverEntry[] }} sl-resize - Emitted when the element is resized.\n */\nexport default class SlResizeObserver extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private resizeObserver: ResizeObserver;\n  private observedElements: HTMLElement[] = [];\n\n  /** Disables the observer. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {\n      this.emit('sl-resize', { detail: { entries } });\n    });\n\n    if (!this.disabled) {\n      this.startObserver();\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.stopObserver();\n  }\n\n  private handleSlotChange() {\n    if (!this.disabled) {\n      this.startObserver();\n    }\n  }\n\n  private startObserver() {\n    const slot = this.shadowRoot!.querySelector('slot');\n\n    if (slot !== null) {\n      const elements = slot.assignedElements({ flatten: true }) as HTMLElement[];\n\n      // Unwatch previous elements\n      this.observedElements.forEach(el => this.resizeObserver.unobserve(el));\n      this.observedElements = [];\n\n      // Watch new elements\n      elements.forEach(el => {\n        this.resizeObserver.observe(el);\n        this.observedElements.push(el);\n      });\n    }\n  }\n\n  private stopObserver() {\n    this.resizeObserver.disconnect();\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    if (this.disabled) {\n      this.stopObserver();\n    } else {\n      this.startObserver();\n    }\n  }\n\n  render() {\n    return html` <slot @slotchange=${this.handleSlotChange}></slot> `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-resize-observer': SlResizeObserver;\n  }\n}\n"
  },
  {
    "path": "src/components/resize-observer/resize-observer.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: contents;\n  }\n`;\n"
  },
  {
    "path": "src/components/resize-observer/resize-observer.ts",
    "content": "import SlResizeObserver from './resize-observer.component.js';\n\nexport * from './resize-observer.component.js';\nexport default SlResizeObserver;\n\nSlResizeObserver.define('sl-resize-observer');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-resize-observer': SlResizeObserver;\n  }\n}\n"
  },
  {
    "path": "src/components/select/select.component.ts",
    "content": "import { animateTo, stopAnimations } from '../../internal/animate.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { scrollIntoView } from '../../internal/scroll.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlPopup from '../popup/popup.component.js';\nimport SlTag from '../tag/tag.component.js';\nimport styles from './select.styles.js';\nimport type { CSSResultGroup, TemplateResult } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\nimport type { SlRemoveEvent } from '../../events/sl-remove.js';\nimport type SlOption from '../option/option.component.js';\n\n/**\n * @summary Selects allow you to choose items from a menu of predefined options.\n * @documentation https://shoelace.style/components/select\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon\n * @dependency sl-popup\n * @dependency sl-tag\n *\n * @slot - The listbox options. Must be `<sl-option>` elements. You can use `<sl-divider>` to group items visually.\n * @slot label - The input's label. Alternatively, you can use the `label` attribute.\n * @slot prefix - Used to prepend a presentational icon or similar element to the combobox.\n * @slot suffix - Used to append a presentational icon or similar element to the combobox.\n * @slot clear-icon - An icon to use in lieu of the default clear icon.\n * @slot expand-icon - The icon to show when the control is expanded and collapsed. Rotates on open and close.\n * @slot help-text - Text that describes how to use the input. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-change - Emitted when the control's value changes.\n * @event sl-clear - Emitted when the control's value is cleared.\n * @event sl-input - Emitted when the control receives input.\n * @event sl-focus - Emitted when the control gains focus.\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-show - Emitted when the select's menu opens.\n * @event sl-after-show - Emitted after the select's menu opens and all animations are complete.\n * @event sl-hide - Emitted when the select's menu closes.\n * @event sl-after-hide - Emitted after the select's menu closes and all animations are complete.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart form-control - The form control that wraps the label, input, and help text.\n * @csspart form-control-label - The label's wrapper.\n * @csspart form-control-input - The select's wrapper.\n * @csspart form-control-help-text - The help text's wrapper.\n * @csspart combobox - The container the wraps the prefix, suffix, combobox, clear icon, and expand button.\n * @csspart prefix - The container that wraps the prefix slot.\n * @csspart suffix - The container that wraps the suffix slot.\n * @csspart display-input - The element that displays the selected option's label, an `<input>` element.\n * @csspart listbox - The listbox container where options are slotted.\n * @csspart tags - The container that houses option tags when `multiselect` is used.\n * @csspart tag - The individual tags that represent each multiselect option.\n * @csspart tag__base - The tag's base part.\n * @csspart tag__content - The tag's content part.\n * @csspart tag__remove-button - The tag's remove button.\n * @csspart tag__remove-button__base - The tag's remove button base part.\n * @csspart clear-button - The clear button.\n * @csspart expand-icon - The container that wraps the expand icon.\n */\nexport default class SlSelect extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n  static dependencies = {\n    'sl-icon': SlIcon,\n    'sl-popup': SlPopup,\n    'sl-tag': SlTag\n  };\n\n  private readonly formControlController = new FormControlController(this, {\n    assumeInteractionOn: ['sl-blur', 'sl-input']\n  });\n  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');\n  private readonly localize = new LocalizeController(this);\n  private typeToSelectString = '';\n  private typeToSelectTimeout: number;\n  private closeWatcher: CloseWatcher | null;\n\n  @query('.select') popup: SlPopup;\n  @query('.select__combobox') combobox: HTMLSlotElement;\n  @query('.select__display-input') displayInput: HTMLInputElement;\n  @query('.select__value-input') valueInput: HTMLInputElement;\n  @query('.select__listbox') listbox: HTMLSlotElement;\n\n  @state() private hasFocus = false;\n  @state() displayLabel = '';\n  @state() currentOption: SlOption;\n  @state() selectedOptions: SlOption[] = [];\n  @state() private valueHasChanged: boolean = false;\n\n  /** The name of the select, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  private _value: string | string[] = '';\n\n  get value() {\n    return this._value;\n  }\n\n  /**\n   * The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the\n   * value attribute will be a space-delimited list of values based on the options selected, and the value property will\n   * be an array. **For this reason, values must not contain spaces.**\n   */\n  @state()\n  set value(val: string | string[]) {\n    if (this.multiple) {\n      val = Array.isArray(val) ? val : val.split(' ');\n    } else {\n      val = Array.isArray(val) ? val.join(' ') : val;\n    }\n\n    if (this._value === val) {\n      return;\n    }\n\n    this.valueHasChanged = true;\n    this._value = val;\n  }\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @property({ attribute: 'value' }) defaultValue: string | string[] = '';\n\n  /** The select's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Placeholder text to show as a hint when the select is empty. */\n  @property() placeholder = '';\n\n  /** Allows more than one option to be selected. */\n  @property({ type: Boolean, reflect: true }) multiple = false;\n\n  /**\n   * The maximum number of selected options to show when `multiple` is true. After the maximum, \"+n\" will be shown to\n   * indicate the number of additional items that are selected. Set to 0 to remove the limit.\n   */\n  @property({ attribute: 'max-options-visible', type: Number }) maxOptionsVisible = 3;\n\n  /** Disables the select control. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Adds a clear button when the select is not empty. */\n  @property({ type: Boolean }) clearable = false;\n\n  /**\n   * Indicates whether or not the select is open. You can toggle this attribute to show and hide the menu, or you can\n   * use the `show()` and `hide()` methods and this attribute will reflect the select's open state.\n   */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /**\n   * Enable this option to prevent the listbox from being clipped when the component is placed inside a container with\n   * `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.\n   */\n  @property({ type: Boolean }) hoist = false;\n\n  /** Draws a filled select. */\n  @property({ type: Boolean, reflect: true }) filled = false;\n\n  /** Draws a pill-style select with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  /** The select's label. If you need to display HTML, use the `label` slot instead. */\n  @property() label = '';\n\n  /**\n   * The preferred placement of the select's menu. Note that the actual placement may vary as needed to keep the listbox\n   * inside of the viewport.\n   */\n  @property({ reflect: true }) placement: 'top' | 'bottom' = 'bottom';\n\n  /** The select's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** The select's required attribute. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /**\n   * A function that customizes the tags to be rendered when multiple=true. The first argument is the option, the second\n   * is the current tag's index.  The function should return either a Lit TemplateResult or a string containing trusted HTML of the symbol to render at\n   * the specified value.\n   */\n  @property() getTag: (option: SlOption, index: number) => TemplateResult | string | HTMLElement = option => {\n    return html`\n      <sl-tag\n        part=\"tag\"\n        exportparts=\"\n              base:tag__base,\n              content:tag__content,\n              remove-button:tag__remove-button,\n              remove-button__base:tag__remove-button__base\n            \"\n        ?pill=${this.pill}\n        size=${this.size}\n        removable\n        @sl-remove=${(event: SlRemoveEvent) => this.handleTagRemove(event, option)}\n      >\n        ${option.getTextLabel()}\n      </sl-tag>\n    `;\n  };\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.valueInput.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.valueInput.validationMessage;\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n\n    setTimeout(() => {\n      this.handleDefaultSlotChange();\n    });\n\n    // Because this is a form control, it shouldn't be opened initially\n    this.open = false;\n  }\n\n  private addOpenListeners() {\n    //\n    // Listen on the root node instead of the document in case the elements are inside a shadow root\n    //\n    // https://github.com/shoelace-style/shoelace/issues/1763\n    //\n    document.addEventListener('focusin', this.handleDocumentFocusIn);\n    document.addEventListener('keydown', this.handleDocumentKeyDown);\n    document.addEventListener('mousedown', this.handleDocumentMouseDown);\n\n    // If the component is rendered in a shadow root, we need to attach the focusin listener there too\n    if (this.getRootNode() !== document) {\n      this.getRootNode().addEventListener('focusin', this.handleDocumentFocusIn);\n    }\n\n    if ('CloseWatcher' in window) {\n      this.closeWatcher?.destroy();\n      this.closeWatcher = new CloseWatcher();\n      this.closeWatcher.onclose = () => {\n        if (this.open) {\n          this.hide();\n          this.displayInput.focus({ preventScroll: true });\n        }\n      };\n    }\n  }\n\n  private removeOpenListeners() {\n    document.removeEventListener('focusin', this.handleDocumentFocusIn);\n    document.removeEventListener('keydown', this.handleDocumentKeyDown);\n    document.removeEventListener('mousedown', this.handleDocumentMouseDown);\n\n    if (this.getRootNode() !== document) {\n      this.getRootNode().removeEventListener('focusin', this.handleDocumentFocusIn);\n    }\n\n    this.closeWatcher?.destroy();\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.displayInput.setSelectionRange(0, 0);\n    this.emit('sl-focus');\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleDocumentFocusIn = (event: KeyboardEvent) => {\n    // Close when focusing out of the select\n    const path = event.composedPath();\n    if (this && !path.includes(this)) {\n      this.hide();\n    }\n  };\n\n  private handleDocumentKeyDown = (event: KeyboardEvent) => {\n    const target = event.target as HTMLElement;\n    const isClearButton = target.closest('.select__clear') !== null;\n    const isIconButton = target.closest('sl-icon-button') !== null;\n\n    // Ignore presses when the target is an icon button (e.g. the remove button in <sl-tag>)\n    if (isClearButton || isIconButton) {\n      return;\n    }\n\n    // Close when pressing escape\n    if (event.key === 'Escape' && this.open && !this.closeWatcher) {\n      event.preventDefault();\n      event.stopPropagation();\n      this.hide();\n      this.displayInput.focus({ preventScroll: true });\n    }\n\n    // Handle enter and space. When pressing space, we allow for type to select behaviors so if there's anything in the\n    // buffer we _don't_ close it.\n    if (event.key === 'Enter' || (event.key === ' ' && this.typeToSelectString === '')) {\n      event.preventDefault();\n      event.stopImmediatePropagation();\n\n      // If it's not open, open it\n      if (!this.open) {\n        this.show();\n        return;\n      }\n\n      // If it is open, update the value based on the current selection and close it\n      if (this.currentOption && !this.currentOption.disabled) {\n        this.valueHasChanged = true;\n        if (this.multiple) {\n          this.toggleOptionSelection(this.currentOption);\n        } else {\n          this.setSelectedOptions(this.currentOption);\n        }\n\n        // Emit after updating\n        this.updateComplete.then(() => {\n          this.emit('sl-input');\n          this.emit('sl-change');\n        });\n\n        if (!this.multiple) {\n          this.hide();\n          this.displayInput.focus({ preventScroll: true });\n        }\n      }\n\n      return;\n    }\n\n    // Navigate options\n    if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {\n      const allOptions = this.getAllOptions();\n      const currentIndex = allOptions.indexOf(this.currentOption);\n      let newIndex = Math.max(0, currentIndex);\n\n      // Prevent scrolling\n      event.preventDefault();\n\n      // Open it\n      if (!this.open) {\n        this.show();\n\n        // If an option is already selected, stop here because we want that one to remain highlighted when the listbox\n        // opens for the first time\n        if (this.currentOption) {\n          return;\n        }\n      }\n\n      if (event.key === 'ArrowDown') {\n        newIndex = currentIndex + 1;\n        if (newIndex > allOptions.length - 1) newIndex = 0;\n      } else if (event.key === 'ArrowUp') {\n        newIndex = currentIndex - 1;\n        if (newIndex < 0) newIndex = allOptions.length - 1;\n      } else if (event.key === 'Home') {\n        newIndex = 0;\n      } else if (event.key === 'End') {\n        newIndex = allOptions.length - 1;\n      }\n\n      this.setCurrentOption(allOptions[newIndex]);\n    }\n\n    // All other \"printable\" keys trigger type to select\n    if ((event.key && event.key.length === 1) || event.key === 'Backspace') {\n      const allOptions = this.getAllOptions();\n\n      // Don't block important key combos like CMD+R\n      if (event.metaKey || event.ctrlKey || event.altKey) {\n        return;\n      }\n\n      // Open, unless the key that triggered is backspace\n      if (!this.open) {\n        if (event.key === 'Backspace') {\n          return;\n        }\n\n        this.show();\n      }\n\n      event.stopPropagation();\n      event.preventDefault();\n\n      clearTimeout(this.typeToSelectTimeout);\n      this.typeToSelectTimeout = window.setTimeout(() => (this.typeToSelectString = ''), 1000);\n\n      if (event.key === 'Backspace') {\n        this.typeToSelectString = this.typeToSelectString.slice(0, -1);\n      } else {\n        this.typeToSelectString += event.key.toLowerCase();\n      }\n\n      for (const option of allOptions) {\n        const label = option.getTextLabel().toLowerCase();\n\n        if (label.startsWith(this.typeToSelectString)) {\n          this.setCurrentOption(option);\n          break;\n        }\n      }\n    }\n  };\n\n  private handleDocumentMouseDown = (event: MouseEvent) => {\n    // Close when clicking outside of the select\n    const path = event.composedPath();\n    if (this && !path.includes(this)) {\n      this.hide();\n    }\n  };\n\n  private handleLabelClick() {\n    this.displayInput.focus();\n  }\n\n  private handleComboboxMouseDown(event: MouseEvent) {\n    const path = event.composedPath();\n    const isIconButton = path.some(el => el instanceof Element && el.tagName.toLowerCase() === 'sl-icon-button');\n\n    // Ignore disabled controls and clicks on tags (remove buttons)\n    if (this.disabled || isIconButton) {\n      return;\n    }\n\n    event.preventDefault();\n    this.displayInput.focus({ preventScroll: true });\n    this.open = !this.open;\n  }\n\n  private handleComboboxKeyDown(event: KeyboardEvent) {\n    if (event.key === 'Tab') {\n      return;\n    }\n\n    event.stopPropagation();\n    this.handleDocumentKeyDown(event);\n  }\n\n  private handleClearClick(event: MouseEvent) {\n    event.stopPropagation();\n\n    this.valueHasChanged = true;\n\n    if (this.value !== '') {\n      this.setSelectedOptions([]);\n      this.displayInput.focus({ preventScroll: true });\n\n      // Emit after update\n      this.updateComplete.then(() => {\n        this.emit('sl-clear');\n        this.emit('sl-input');\n        this.emit('sl-change');\n      });\n    }\n  }\n\n  private handleClearMouseDown(event: MouseEvent) {\n    // Don't lose focus or propagate events when clicking the clear button\n    event.stopPropagation();\n    event.preventDefault();\n  }\n\n  private handleOptionClick(event: MouseEvent) {\n    const target = event.target as HTMLElement;\n    const option = target.closest('sl-option');\n    const oldValue = this.value;\n\n    if (option && !option.disabled) {\n      this.valueHasChanged = true;\n      if (this.multiple) {\n        this.toggleOptionSelection(option);\n      } else {\n        this.setSelectedOptions(option);\n      }\n\n      // Set focus after updating so the value is announced by screen readers\n      this.updateComplete.then(() => this.displayInput.focus({ preventScroll: true }));\n\n      if (this.value !== oldValue) {\n        // Emit after updating\n        this.updateComplete.then(() => {\n          this.emit('sl-input');\n          this.emit('sl-change');\n        });\n      }\n\n      if (!this.multiple) {\n        this.hide();\n        this.displayInput.focus({ preventScroll: true });\n      }\n    }\n  }\n\n  /* @internal - used by options to update labels */\n  public handleDefaultSlotChange() {\n    if (!customElements.get('sl-option')) {\n      customElements.whenDefined('sl-option').then(() => this.handleDefaultSlotChange());\n    }\n\n    const allOptions = this.getAllOptions();\n    const val = this.valueHasChanged ? this.value : this.defaultValue;\n    const value = Array.isArray(val) ? val : [val];\n    const values: string[] = [];\n\n    // Check for duplicate values in menu items\n    allOptions.forEach(option => values.push(option.value));\n\n    // Select only the options that match the new value\n    this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));\n  }\n\n  private handleTagRemove(event: SlRemoveEvent, option: SlOption) {\n    event.stopPropagation();\n\n    this.valueHasChanged = true;\n\n    if (!this.disabled) {\n      this.toggleOptionSelection(option, false);\n\n      // Emit after updating\n      this.updateComplete.then(() => {\n        this.emit('sl-input');\n        this.emit('sl-change');\n      });\n    }\n  }\n\n  // Gets an array of all <sl-option> elements\n  private getAllOptions() {\n    return [...this.querySelectorAll<SlOption>('sl-option')];\n  }\n\n  // Gets the first <sl-option> element\n  private getFirstOption() {\n    return this.querySelector<SlOption>('sl-option');\n  }\n\n  // Sets the current option, which is the option the user is currently interacting with (e.g. via keyboard). Only one\n  // option may be \"current\" at a time.\n  private setCurrentOption(option: SlOption | null) {\n    const allOptions = this.getAllOptions();\n\n    // Clear selection\n    allOptions.forEach(el => {\n      el.current = false;\n      el.tabIndex = -1;\n    });\n\n    // Select the target option\n    if (option) {\n      this.currentOption = option;\n      option.current = true;\n      option.tabIndex = 0;\n      option.focus();\n    }\n  }\n\n  // Sets the selected option(s)\n  private setSelectedOptions(option: SlOption | SlOption[]) {\n    const allOptions = this.getAllOptions();\n    const newSelectedOptions = Array.isArray(option) ? option : [option];\n\n    // Clear existing selection\n    allOptions.forEach(el => (el.selected = false));\n\n    // Set the new selection\n    if (newSelectedOptions.length) {\n      newSelectedOptions.forEach(el => (el.selected = true));\n    }\n\n    // Update selection, value, and display label\n    this.selectionChanged();\n  }\n\n  // Toggles an option's selected state\n  private toggleOptionSelection(option: SlOption, force?: boolean) {\n    if (force === true || force === false) {\n      option.selected = force;\n    } else {\n      option.selected = !option.selected;\n    }\n\n    this.selectionChanged();\n  }\n\n  // This method must be called whenever the selection changes. It will update the selected options cache, the current\n  // value, and the display value\n  private selectionChanged() {\n    const options = this.getAllOptions();\n    // Update selected options cache\n    this.selectedOptions = options.filter(el => el.selected);\n\n    // Keep a reference to the previous `valueHasChanged`. Changes made here don't count has changing the value.\n    const cachedValueHasChanged = this.valueHasChanged;\n\n    // Update the value and display label\n    if (this.multiple) {\n      this.value = this.selectedOptions.map(el => el.value);\n\n      if (this.placeholder && this.value.length === 0) {\n        // When no items are selected, keep the value empty so the placeholder shows\n        this.displayLabel = '';\n      } else {\n        this.displayLabel = this.localize.term('numOptionsSelected', this.selectedOptions.length);\n      }\n    } else {\n      const selectedOption = this.selectedOptions[0];\n      this.value = selectedOption?.value ?? '';\n      this.displayLabel = selectedOption?.getTextLabel?.() ?? '';\n    }\n    this.valueHasChanged = cachedValueHasChanged;\n\n    // Update validity\n    this.updateComplete.then(() => {\n      this.formControlController.updateValidity();\n    });\n  }\n\n  protected get tags() {\n    return this.selectedOptions.map((option, index) => {\n      if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {\n        const tag = this.getTag(option, index);\n        // Wrap so we can handle the remove\n        return html`<div @sl-remove=${(e: SlRemoveEvent) => this.handleTagRemove(e, option)}>\n          ${typeof tag === 'string' ? unsafeHTML(tag) : tag}\n        </div>`;\n      } else if (index === this.maxOptionsVisible) {\n        // Hit tag limit\n        return html`<sl-tag size=${this.size}>+${this.selectedOptions.length - index}</sl-tag>`;\n      }\n      return html``;\n    });\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Close the listbox when the control is disabled\n    if (this.disabled) {\n      this.open = false;\n      this.handleOpenChange();\n    }\n  }\n\n  attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {\n    super.attributeChangedCallback(name, oldVal, newVal);\n\n    /** This is a backwards compatibility call. In a new major version we should make a clean separation between \"value\" the attribute mapping to \"defaultValue\" property and \"value\" the property not reflecting. */\n    if (name === 'value') {\n      const cachedValueHasChanged = this.valueHasChanged;\n      this.value = this.defaultValue;\n\n      // Set it back to false since this isn't an interaction.\n      this.valueHasChanged = cachedValueHasChanged;\n    }\n  }\n\n  @watch(['defaultValue', 'value'], { waitUntilFirstUpdate: true })\n  handleValueChange() {\n    if (!this.valueHasChanged) {\n      const cachedValueHasChanged = this.valueHasChanged;\n      this.value = this.defaultValue;\n\n      // Set it back to false since this isn't an interaction.\n      this.valueHasChanged = cachedValueHasChanged;\n    }\n\n    const allOptions = this.getAllOptions();\n    const value = Array.isArray(this.value) ? this.value : [this.value];\n\n    // Select only the options that match the new value\n    this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));\n  }\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open && !this.disabled) {\n      // Reset the current option\n      this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption());\n\n      // Show\n      this.emit('sl-show');\n      this.addOpenListeners();\n\n      await stopAnimations(this);\n      this.listbox.hidden = false;\n      this.popup.active = true;\n\n      // Select the appropriate option based on value after the listbox opens\n      requestAnimationFrame(() => {\n        this.setCurrentOption(this.currentOption);\n      });\n\n      const { keyframes, options } = getAnimation(this, 'select.show', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n\n      // Make sure the current option is scrolled into view (required for Safari)\n      if (this.currentOption) {\n        scrollIntoView(this.currentOption, this.listbox, 'vertical', 'auto');\n      }\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      this.emit('sl-hide');\n      this.removeOpenListeners();\n\n      await stopAnimations(this);\n      const { keyframes, options } = getAnimation(this, 'select.hide', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n      this.listbox.hidden = true;\n      this.popup.active = false;\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  /** Shows the listbox. */\n  async show() {\n    if (this.open || this.disabled) {\n      this.open = false;\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the listbox. */\n  async hide() {\n    if (!this.open || this.disabled) {\n      this.open = false;\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.valueInput.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.valueInput.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.valueInput.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  /** Sets focus on the control. */\n  focus(options?: FocusOptions) {\n    this.displayInput.focus(options);\n  }\n\n  /** Removes focus from the control. */\n  blur() {\n    this.displayInput.blur();\n  }\n\n  render() {\n    const hasLabelSlot = this.hasSlotController.test('label');\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasLabel = this.label ? true : !!hasLabelSlot;\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n    const hasClearIcon = this.clearable && !this.disabled && this.value.length > 0;\n    const isPlaceholderVisible = this.placeholder && this.value && this.value.length <= 0;\n\n    return html`\n      <div\n        part=\"form-control\"\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--has-label': hasLabel,\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          id=\"label\"\n          part=\"form-control-label\"\n          class=\"form-control__label\"\n          aria-hidden=${hasLabel ? 'false' : 'true'}\n          @click=${this.handleLabelClick}\n        >\n          <slot name=\"label\">${this.label}</slot>\n        </label>\n\n        <div part=\"form-control-input\" class=\"form-control-input\">\n          <sl-popup\n            class=${classMap({\n              select: true,\n              'select--standard': true,\n              'select--filled': this.filled,\n              'select--pill': this.pill,\n              'select--open': this.open,\n              'select--disabled': this.disabled,\n              'select--multiple': this.multiple,\n              'select--focused': this.hasFocus,\n              'select--placeholder-visible': isPlaceholderVisible,\n              'select--top': this.placement === 'top',\n              'select--bottom': this.placement === 'bottom',\n              'select--small': this.size === 'small',\n              'select--medium': this.size === 'medium',\n              'select--large': this.size === 'large'\n            })}\n            placement=${this.placement}\n            strategy=${this.hoist ? 'fixed' : 'absolute'}\n            flip\n            shift\n            sync=\"width\"\n            auto-size=\"vertical\"\n            auto-size-padding=\"10\"\n          >\n            <div\n              part=\"combobox\"\n              class=\"select__combobox\"\n              slot=\"anchor\"\n              @keydown=${this.handleComboboxKeyDown}\n              @mousedown=${this.handleComboboxMouseDown}\n            >\n              <slot part=\"prefix\" name=\"prefix\" class=\"select__prefix\"></slot>\n\n              <input\n                part=\"display-input\"\n                class=\"select__display-input\"\n                type=\"text\"\n                placeholder=${this.placeholder}\n                .disabled=${this.disabled}\n                .value=${this.displayLabel}\n                autocomplete=\"off\"\n                spellcheck=\"false\"\n                autocapitalize=\"off\"\n                readonly\n                aria-controls=\"listbox\"\n                aria-expanded=${this.open ? 'true' : 'false'}\n                aria-haspopup=\"listbox\"\n                aria-labelledby=\"label\"\n                aria-disabled=${this.disabled ? 'true' : 'false'}\n                aria-describedby=\"help-text\"\n                role=\"combobox\"\n                tabindex=\"0\"\n                @focus=${this.handleFocus}\n                @blur=${this.handleBlur}\n              />\n\n              ${this.multiple ? html`<div part=\"tags\" class=\"select__tags\">${this.tags}</div>` : ''}\n\n              <input\n                class=\"select__value-input\"\n                type=\"text\"\n                ?disabled=${this.disabled}\n                ?required=${this.required}\n                .value=${Array.isArray(this.value) ? this.value.join(', ') : this.value}\n                tabindex=\"-1\"\n                aria-hidden=\"true\"\n                @focus=${() => this.focus()}\n                @invalid=${this.handleInvalid}\n              />\n\n              ${hasClearIcon\n                ? html`\n                    <button\n                      part=\"clear-button\"\n                      class=\"select__clear\"\n                      type=\"button\"\n                      aria-label=${this.localize.term('clearEntry')}\n                      @mousedown=${this.handleClearMouseDown}\n                      @click=${this.handleClearClick}\n                      tabindex=\"-1\"\n                    >\n                      <slot name=\"clear-icon\">\n                        <sl-icon name=\"x-circle-fill\" library=\"system\"></sl-icon>\n                      </slot>\n                    </button>\n                  `\n                : ''}\n\n              <slot name=\"suffix\" part=\"suffix\" class=\"select__suffix\"></slot>\n\n              <slot name=\"expand-icon\" part=\"expand-icon\" class=\"select__expand-icon\">\n                <sl-icon library=\"system\" name=\"chevron-down\"></sl-icon>\n              </slot>\n            </div>\n\n            <div\n              id=\"listbox\"\n              role=\"listbox\"\n              aria-expanded=${this.open ? 'true' : 'false'}\n              aria-multiselectable=${this.multiple ? 'true' : 'false'}\n              aria-labelledby=\"label\"\n              part=\"listbox\"\n              class=\"select__listbox\"\n              tabindex=\"-1\"\n              @mouseup=${this.handleOptionClick}\n              @slotchange=${this.handleDefaultSlotChange}\n            >\n              <slot></slot>\n            </div>\n          </sl-popup>\n        </div>\n\n        <div\n          part=\"form-control-help-text\"\n          id=\"help-text\"\n          class=\"form-control__help-text\"\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n\nsetDefaultAnimation('select.show', {\n  keyframes: [\n    { opacity: 0, scale: 0.9 },\n    { opacity: 1, scale: 1 }\n  ],\n  options: { duration: 100, easing: 'ease' }\n});\n\nsetDefaultAnimation('select.hide', {\n  keyframes: [\n    { opacity: 1, scale: 1 },\n    { opacity: 0, scale: 0.9 }\n  ],\n  options: { duration: 100, easing: 'ease' }\n});\n"
  },
  {
    "path": "src/components/select/select.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  /** The popup */\n  .select {\n    flex: 1 1 auto;\n    display: inline-flex;\n    width: 100%;\n    position: relative;\n    vertical-align: middle;\n  }\n\n  .select::part(popup) {\n    z-index: var(--sl-z-index-dropdown);\n  }\n\n  .select[data-current-placement^='top']::part(popup) {\n    transform-origin: bottom;\n  }\n\n  .select[data-current-placement^='bottom']::part(popup) {\n    transform-origin: top;\n  }\n\n  /* Combobox */\n  .select__combobox {\n    flex: 1;\n    display: flex;\n    width: 100%;\n    min-width: 0;\n    position: relative;\n    align-items: center;\n    justify-content: start;\n    font-family: var(--sl-input-font-family);\n    font-weight: var(--sl-input-font-weight);\n    letter-spacing: var(--sl-input-letter-spacing);\n    vertical-align: middle;\n    overflow: hidden;\n    cursor: pointer;\n    transition:\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) border,\n      var(--sl-transition-fast) box-shadow,\n      var(--sl-transition-fast) background-color;\n  }\n\n  .select__display-input {\n    position: relative;\n    width: 100%;\n    font: inherit;\n    border: none;\n    background: none;\n    color: var(--sl-input-color);\n    cursor: inherit;\n    overflow: hidden;\n    padding: 0;\n    margin: 0;\n    -webkit-appearance: none;\n  }\n\n  .select__display-input::placeholder {\n    color: var(--sl-input-placeholder-color);\n  }\n\n  .select:not(.select--disabled):hover .select__display-input {\n    color: var(--sl-input-color-hover);\n  }\n\n  .select__display-input:focus {\n    outline: none;\n  }\n\n  /* Visually hide the display input when multiple is enabled */\n  .select--multiple:not(.select--placeholder-visible) .select__display-input {\n    position: absolute;\n    z-index: -1;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    opacity: 0;\n  }\n\n  .select__value-input {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    padding: 0;\n    margin: 0;\n    opacity: 0;\n    z-index: -1;\n  }\n\n  .select__tags {\n    display: flex;\n    flex: 1;\n    align-items: center;\n    flex-wrap: wrap;\n    margin-inline-start: var(--sl-spacing-2x-small);\n  }\n\n  .select__tags::slotted(sl-tag) {\n    cursor: pointer !important;\n  }\n\n  .select--disabled .select__tags,\n  .select--disabled .select__tags::slotted(sl-tag) {\n    cursor: not-allowed !important;\n  }\n\n  /* Standard selects */\n  .select--standard .select__combobox {\n    background-color: var(--sl-input-background-color);\n    border: solid var(--sl-input-border-width) var(--sl-input-border-color);\n  }\n\n  .select--standard.select--disabled .select__combobox {\n    background-color: var(--sl-input-background-color-disabled);\n    border-color: var(--sl-input-border-color-disabled);\n    color: var(--sl-input-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n    outline: none;\n  }\n\n  .select--standard:not(.select--disabled).select--open .select__combobox,\n  .select--standard:not(.select--disabled).select--focused .select__combobox {\n    background-color: var(--sl-input-background-color-focus);\n    border-color: var(--sl-input-border-color-focus);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);\n  }\n\n  /* Filled selects */\n  .select--filled .select__combobox {\n    border: none;\n    background-color: var(--sl-input-filled-background-color);\n    color: var(--sl-input-color);\n  }\n\n  .select--filled:hover:not(.select--disabled) .select__combobox {\n    background-color: var(--sl-input-filled-background-color-hover);\n  }\n\n  .select--filled.select--disabled .select__combobox {\n    background-color: var(--sl-input-filled-background-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .select--filled:not(.select--disabled).select--open .select__combobox,\n  .select--filled:not(.select--disabled).select--focused .select__combobox {\n    background-color: var(--sl-input-filled-background-color-focus);\n    outline: var(--sl-focus-ring);\n  }\n\n  /* Sizes */\n  .select--small .select__combobox {\n    border-radius: var(--sl-input-border-radius-small);\n    font-size: var(--sl-input-font-size-small);\n    min-height: var(--sl-input-height-small);\n    padding-block: 0;\n    padding-inline: var(--sl-input-spacing-small);\n  }\n\n  .select--small .select__clear {\n    margin-inline-start: var(--sl-input-spacing-small);\n  }\n\n  .select--small .select__prefix::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-small);\n  }\n\n  .select--small.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-small);\n  }\n\n  .select--small.select--multiple:not(.select--placeholder-visible) .select__combobox {\n    padding-block: 2px;\n    padding-inline-start: 0;\n  }\n\n  .select--small .select__tags {\n    gap: 2px;\n  }\n\n  .select--medium .select__combobox {\n    border-radius: var(--sl-input-border-radius-medium);\n    font-size: var(--sl-input-font-size-medium);\n    min-height: var(--sl-input-height-medium);\n    padding-block: 0;\n    padding-inline: var(--sl-input-spacing-medium);\n  }\n\n  .select--medium .select__clear {\n    margin-inline-start: var(--sl-input-spacing-medium);\n  }\n\n  .select--medium .select__prefix::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-medium);\n  }\n\n  .select--medium.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-medium);\n  }\n\n  .select--medium.select--multiple:not(.select--placeholder-visible) .select__combobox {\n    padding-inline-start: 0;\n    padding-block: 3px;\n  }\n\n  .select--medium .select__tags {\n    gap: 3px;\n  }\n\n  .select--large .select__combobox {\n    border-radius: var(--sl-input-border-radius-large);\n    font-size: var(--sl-input-font-size-large);\n    min-height: var(--sl-input-height-large);\n    padding-block: 0;\n    padding-inline: var(--sl-input-spacing-large);\n  }\n\n  .select--large .select__clear {\n    margin-inline-start: var(--sl-input-spacing-large);\n  }\n\n  .select--large .select__prefix::slotted(*) {\n    margin-inline-end: var(--sl-input-spacing-large);\n  }\n\n  .select--large.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {\n    margin-inline-start: var(--sl-input-spacing-large);\n  }\n\n  .select--large.select--multiple:not(.select--placeholder-visible) .select__combobox {\n    padding-inline-start: 0;\n    padding-block: 4px;\n  }\n\n  .select--large .select__tags {\n    gap: 4px;\n  }\n\n  /* Pills */\n  .select--pill.select--small .select__combobox {\n    border-radius: var(--sl-input-height-small);\n  }\n\n  .select--pill.select--medium .select__combobox {\n    border-radius: var(--sl-input-height-medium);\n  }\n\n  .select--pill.select--large .select__combobox {\n    border-radius: var(--sl-input-height-large);\n  }\n\n  /* Prefix and Suffix */\n  .select__prefix,\n  .select__suffix {\n    flex: 0;\n    display: inline-flex;\n    align-items: center;\n    color: var(--sl-input-placeholder-color);\n  }\n\n  .select__suffix::slotted(*) {\n    margin-inline-start: var(--sl-spacing-small);\n  }\n\n  /* Clear button */\n  .select__clear {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    font-size: inherit;\n    color: var(--sl-input-icon-color);\n    border: none;\n    background: none;\n    padding: 0;\n    transition: var(--sl-transition-fast) color;\n    cursor: pointer;\n  }\n\n  .select__clear:hover {\n    color: var(--sl-input-icon-color-hover);\n  }\n\n  .select__clear:focus {\n    outline: none;\n  }\n\n  /* Expand icon */\n  .select__expand-icon {\n    flex: 0 0 auto;\n    display: flex;\n    align-items: center;\n    transition: var(--sl-transition-medium) rotate ease;\n    rotate: 0;\n    margin-inline-start: var(--sl-spacing-small);\n  }\n\n  .select--open .select__expand-icon {\n    rotate: -180deg;\n  }\n\n  /* Listbox */\n  .select__listbox {\n    display: block;\n    position: relative;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    box-shadow: var(--sl-shadow-large);\n    background: var(--sl-panel-background-color);\n    border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);\n    border-radius: var(--sl-border-radius-medium);\n    padding-block: var(--sl-spacing-x-small);\n    padding-inline: 0;\n    overflow: auto;\n    overscroll-behavior: none;\n\n    /* Make sure it adheres to the popup's auto size */\n    max-width: var(--auto-size-available-width);\n    max-height: var(--auto-size-available-height);\n  }\n\n  .select__listbox ::slotted(sl-divider) {\n    --spacing: var(--sl-spacing-x-small);\n  }\n\n  .select__listbox ::slotted(small) {\n    display: block;\n    font-size: var(--sl-font-size-small);\n    font-weight: var(--sl-font-weight-semibold);\n    color: var(--sl-color-neutral-500);\n    padding-block: var(--sl-spacing-2x-small);\n    padding-inline: var(--sl-spacing-x-large);\n  }\n`;\n"
  },
  {
    "path": "src/components/select/select.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { clickOnElement } from '../../internal/test.js';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport { serialize } from '../../utilities/form.js';\nimport sinon from 'sinon';\nimport type SlOption from '../option/option.js';\nimport type SlSelect from './select.js';\n\ndescribe('<sl-select>', () => {\n  describe('accessibility', () => {\n    it('should pass accessibility tests when closed', async () => {\n      const select = await fixture<SlSelect>(html`\n        <sl-select label=\"Select one\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      await expect(select).to.be.accessible();\n    });\n\n    it('should pass accessibility tests when open', async () => {\n      const select = await fixture<SlSelect>(html`\n        <sl-select label=\"Select one\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n\n      await select.show();\n\n      await expect(select).to.be.accessible();\n    });\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select disabled>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    expect(el.displayInput.disabled).to.be.true;\n  });\n\n  it('should show a placeholder when no options are selected', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select placeholder=\"Select one\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('[part~=\"display-input\"]')!;\n\n    expect(getComputedStyle(displayInput).opacity).to.not.equal('0');\n    expect(displayInput.placeholder).to.equal('Select one');\n  });\n\n  it('should show a placeholder when no options are selected and multiple is set', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select placeholder=\"Select a few\" multiple>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('[part~=\"display-input\"]')!;\n\n    expect(getComputedStyle(displayInput).opacity).to.not.equal('0');\n    expect(displayInput.placeholder).to.equal('Select a few');\n  });\n\n  it('should not allow selection when the option is disabled', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\" disabled>Option 2</sl-option>\n      </sl-select>\n    `);\n    const disabledOption = el.querySelector('sl-option[disabled]')!;\n\n    await el.show();\n    await clickOnElement(disabledOption);\n    await el.updateComplete;\n\n    expect(el.value).to.equal('option-1');\n  });\n\n  it('should focus the select when clicking on the label', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select label=\"Select One\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const label = el.shadowRoot!.querySelector('[part~=\"form-control-label\"]')!;\n    const submitHandler = sinon.spy();\n\n    el.addEventListener('sl-focus', submitHandler);\n    (label as HTMLLabelElement).click();\n    await waitUntil(() => submitHandler.calledOnce);\n\n    expect(submitHandler).to.have.been.calledOnce;\n  });\n\n  describe('when the value changes', () => {\n    it('should emit sl-change when the value is changed with the mouse', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select value=\"option-1\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      const secondOption = el.querySelectorAll<SlOption>('sl-option')[1];\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      await el.show();\n      await clickOnElement(secondOption);\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n      expect(el.value).to.equal('option-2');\n    });\n\n    it('should emit sl-change and sl-input when the value is changed with the keyboard', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select value=\"option-1\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      const changeHandler = sinon.spy();\n      const inputHandler = sinon.spy();\n\n      el.addEventListener('sl-change', changeHandler);\n      el.addEventListener('sl-input', inputHandler);\n\n      el.focus();\n      await el.updateComplete;\n      await sendKeys({ press: ' ' }); // open the dropdown\n      await aTimeout(500); // wait for the dropdown to open\n      await sendKeys({ press: 'ArrowDown' }); // move selection to the second option\n      await el.updateComplete;\n      await sendKeys({ press: 'ArrowDown' }); // move selection to the third option\n      await el.updateComplete;\n      el.focus(); // For some reason, the browser loses focus before we press enter. Refocus the select.\n      await sendKeys({ press: 'Enter' }); // commit the selection\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledOnce;\n      expect(el.value).to.equal('option-3');\n    });\n\n    it('should not emit sl-change or sl-input when the value is changed programmatically', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select value=\"option-1\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.value = 'option-2';\n\n      await el.updateComplete;\n    });\n\n    it('should emit sl-change and sl-input with the correct validation message when the value changes', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select required>\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      const option2 = el.querySelectorAll('sl-option')[1];\n      const handler = sinon.spy((event: CustomEvent) => {\n        if (el.validationMessage) {\n          expect.fail(`Validation message should be empty when ${event.type} is emitted and a value is set`);\n        }\n      });\n\n      el.addEventListener('sl-change', handler);\n      el.addEventListener('sl-input', handler);\n\n      await clickOnElement(el);\n      await aTimeout(500);\n      await clickOnElement(option2);\n      await el.updateComplete;\n\n      expect(handler).to.be.calledTwice;\n    });\n\n    // this can happen in on ms-edge autofilling an associated input element in the same form\n    // https://github.com/shoelace-style/shoelace/issues/2117\n    it('should not throw on incomplete events', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select required>\n          <sl-option value=\"option-1\">Option 1</sl-option>\n        </sl-select>\n      `);\n\n      const event = new KeyboardEvent('keydown');\n      Object.defineProperty(event, 'target', { writable: false, value: el });\n      Object.defineProperty(event, 'key', { writable: false, value: undefined });\n\n      /**\n       * If Edge does autofill, it creates a broken KeyboardEvent\n       * which is missing the key value.\n       * Using the normal dispatch mechanism does not allow to do this\n       * Thus passing the event directly to the private method for testing\n       *\n       * @ts-expect-error */\n      el.handleDocumentKeyDown(event);\n    });\n  });\n\n  it('should open the listbox when any letter key is pressed with sl-select is on focus', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const displayInput = el.shadowRoot!.querySelector<HTMLSelectElement>('.select__display-input')!;\n\n    el.focus();\n    await sendKeys({ press: 'r' });\n    await el.updateComplete;\n\n    expect(displayInput.getAttribute('aria-expanded')).to.equal('true');\n  });\n\n  it('should not open the listbox when ctrl + R is pressed with sl-select is on focus', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const displayInput = el.shadowRoot!.querySelector<HTMLSelectElement>('.select__display-input')!;\n\n    el.focus();\n    await sendKeys({ down: 'Control' });\n    await sendKeys({ press: 'r' });\n    await sendKeys({ up: 'Control' });\n    await el.updateComplete;\n    expect(displayInput.getAttribute('aria-expanded')).to.equal('false');\n  });\n\n  describe('when using constraint validation', () => {\n    it('should be valid by default', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const select = el.querySelector<SlSelect>('sl-select')!;\n      expect(select.checkValidity()).to.be.true;\n    });\n\n    it('should be invalid when required and empty', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select required>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const select = el.querySelector<SlSelect>('sl-select')!;\n      expect(select.checkValidity()).to.be.false;\n    });\n\n    it('should focus on the displayInput when constraint validation occurs', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select required>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const select = el.querySelector<SlSelect>('sl-select')!;\n      el.requestSubmit();\n      expect(select.shadowRoot!.activeElement).to.equal(select.displayInput);\n    });\n\n    it('should receive the correct validation attributes (\"states\") when valid', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select label=\"Select one\" required value=\"option-1\">\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      const secondOption = el.querySelectorAll('sl-option')[1]!;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.false;\n      expect(el.hasAttribute('data-valid')).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      await el.show();\n      await clickOnElement(secondOption);\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.true;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when invalid', async () => {\n      const el = await fixture<SlSelect>(html`\n        <sl-select label=\"Select one\" required>\n          <sl-option value=\"option-1\">Option 1</sl-option>\n          <sl-option value=\"option-2\">Option 2</sl-option>\n          <sl-option value=\"option-3\">Option 3</sl-option>\n        </sl-select>\n      `);\n      const secondOption = el.querySelectorAll('sl-option')[1]!;\n\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.true;\n      expect(el.hasAttribute('data-valid')).to.be.false;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      await el.show();\n      await clickOnElement(secondOption);\n      el.value = '';\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.hasAttribute('data-user-invalid')).to.be.true;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <form novalidate>\n          <sl-select required>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const select = el.querySelector<SlSelect>('sl-select')!;\n\n      expect(select.hasAttribute('data-required')).to.be.true;\n      expect(select.hasAttribute('data-optional')).to.be.false;\n      expect(select.hasAttribute('data-invalid')).to.be.true;\n      expect(select.hasAttribute('data-valid')).to.be.false;\n      expect(select.hasAttribute('data-user-invalid')).to.be.false;\n      expect(select.hasAttribute('data-user-valid')).to.be.false;\n    });\n  });\n\n  describe('when submitting a form', () => {\n    it('should serialize its name and value with FormData', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select name=\"a\" value=\"option-1\">\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const formData = new FormData(form);\n      expect(formData.get('a')).to.equal('option-1');\n    });\n\n    it('should serialize its name and value in FormData when multiple options are selected', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select name=\"a\" value=\"option-2 option-3\" multiple>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const formData = new FormData(form);\n      expect(formData.getAll('a')).to.include('option-2');\n      expect(formData.getAll('a')).to.include('option-3');\n    });\n\n    it('should serialize its name and value in JSON', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select name=\"a\" value=\"option-1\">\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const json = serialize(form);\n      expect(json.a).to.equal('option-1');\n    });\n\n    it('should serialize its name and value in JSON when multiple options are selected', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select name=\"a\" value=\"option-2 option-3\" multiple>\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </form>\n      `);\n      const json = serialize(form);\n      expect(JSON.stringify(json)).to.equal(JSON.stringify({ a: ['option-2', 'option-3'] }));\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-select form=\"f\" name=\"a\" value=\"option-1\">\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('option-1');\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-select value=\"option-1\">\n            <sl-option value=\"option-1\">Option 1</sl-option>\n            <sl-option value=\"option-2\">Option 2</sl-option>\n            <sl-option value=\"option-3\">Option 3</sl-option>\n          </sl-select>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const resetButton = form.querySelector('sl-button')!;\n      const select = form.querySelector('sl-select')!;\n\n      select.value = 'option-3';\n      await select.updateComplete;\n      expect(select.value).to.equal('option-3');\n\n      setTimeout(() => resetButton.click());\n      await oneEvent(form, 'reset');\n      await select.updateComplete;\n      expect(select.value).to.equal('option-1');\n    });\n  });\n\n  it('should update the display label when an option changes', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const displayInput = el.shadowRoot!.querySelector<HTMLSelectElement>('.select__display-input')!;\n    const option = el.querySelector('sl-option')!;\n\n    expect(displayInput.value).to.equal('Option 1');\n\n    option.textContent = 'updated';\n\n    await aTimeout(250);\n    await option.updateComplete;\n    await el.updateComplete;\n\n    expect(displayInput.value).to.equal('updated');\n  });\n\n  it('should emit sl-focus and sl-blur when receiving and losing focus', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const focusHandler = sinon.spy();\n    const blurHandler = sinon.spy();\n\n    el.addEventListener('sl-focus', focusHandler);\n    el.addEventListener('sl-blur', blurHandler);\n\n    el.focus();\n    await el.updateComplete;\n    el.blur();\n    await el.updateComplete;\n\n    expect(focusHandler).to.have.been.calledOnce;\n    expect(blurHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-clear when the clear button is clicked', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1\" clearable>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const clearHandler = sinon.spy();\n    const clearButton = el.shadowRoot!.querySelector('[part~=\"clear-button\"]')!;\n\n    el.addEventListener('sl-clear', clearHandler);\n    await el.show();\n    await clickOnElement(clearButton);\n    await el.updateComplete;\n\n    expect(clearHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-change and sl-input when a tag is removed', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1 option-2 option-3\" multiple>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n    const tag = el.shadowRoot!.querySelector('[part~=\"tag\"]')!;\n    const removeButton = tag.shadowRoot!.querySelector('[part~=\"remove-button\"]')!;\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n\n    await clickOnElement(removeButton);\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n  });\n\n  it('should emit sl-show, sl-after-show, sl-hide, and sl-after-hide events when the listbox opens and closes', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1\">\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n\n    await el.show();\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n\n    await el.hide();\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n  });\n\n  it('should have rounded tags when using the pill attribute', async () => {\n    const el = await fixture<SlSelect>(html`\n      <sl-select value=\"option-1 option-2\" multiple pill>\n        <sl-option value=\"option-1\">Option 1</sl-option>\n        <sl-option value=\"option-2\">Option 2</sl-option>\n        <sl-option value=\"option-3\">Option 3</sl-option>\n      </sl-select>\n    `);\n    const tag = el.shadowRoot!.querySelector('[part~=\"tag\"]')!;\n\n    expect(tag.hasAttribute('pill')).to.be.true;\n  });\n  describe('With lazily loaded options', () => {\n    describe('With no existing options', () => {\n      it('Should wait to select the option when the option exists for single select', async () => {\n        const form = await fixture<HTMLFormElement>(\n          html`<form><sl-select name=\"select\" value=\"option-1\"></sl-select></form>`\n        );\n        const el = form.querySelector<SlSelect>('sl-select')!;\n\n        await aTimeout(10);\n        expect(el.value).to.equal('');\n        expect(new FormData(form).get('select')).equal('');\n\n        const option = document.createElement('sl-option');\n        option.value = 'option-1';\n        option.innerText = 'Option 1';\n        el.append(option);\n\n        await aTimeout(10);\n        await el.updateComplete;\n        expect(el.value).to.equal('option-1');\n        expect(new FormData(form).get('select')).equal('option-1');\n      });\n\n      it('Should wait to select the option when the option exists for multiple select', async () => {\n        const form = await fixture<HTMLFormElement>(\n          html`<form><sl-select name=\"select\" value=\"option-1\" multiple></sl-select></form>`\n        );\n\n        const el = form.querySelector<SlSelect>('sl-select')!;\n        expect(Array.isArray(el.value)).to.equal(true);\n        expect(el.value.length).to.equal(0);\n\n        const option = document.createElement('sl-option');\n        option.value = 'option-1';\n        option.innerText = 'Option 1';\n        el.append(option);\n\n        await aTimeout(10);\n        await el.updateComplete;\n        expect(el.value.length).to.equal(1);\n        expect(el.value).to.have.members(['option-1']);\n        expect(new FormData(form).getAll('select')).have.members(['option-1']);\n      });\n    });\n\n    describe('With existing options', () => {\n      it('Should not select the option if options already exist for single select', async () => {\n        const form = await fixture<HTMLFormElement>(\n          html` <form>\n            <sl-select name=\"select\" value=\"foo\">\n              <sl-option value=\"bar\">Bar</sl-option>\n              <sl-option value=\"baz\">Baz</sl-option>\n            </sl-select>\n          </form>`\n        );\n\n        const el = form.querySelector<SlSelect>('sl-select')!;\n        expect(el.value).to.equal('');\n        expect(new FormData(form).get('select')).to.equal('');\n\n        const option = document.createElement('sl-option');\n        option.value = 'foo';\n        option.innerText = 'Foo';\n        el.append(option);\n\n        await aTimeout(10);\n        await el.updateComplete;\n        expect(el.value).to.equal('foo');\n        expect(new FormData(form).get('select')).to.equal('foo');\n      });\n\n      it('Should not select the option if options already exists for multiple select', async () => {\n        const form = await fixture<HTMLFormElement>(\n          html` <form>\n            <sl-select name=\"select\" value=\"foo\" multiple>\n              <sl-option value=\"bar\">Bar</sl-option>\n              <sl-option value=\"baz\">Baz</sl-option>\n            </sl-select>\n          </form>`\n        );\n\n        const el = form.querySelector<SlSelect>('sl-select')!;\n        expect(el.value).to.be.an('array');\n        expect(el.value.length).to.equal(0);\n\n        const option = document.createElement('sl-option');\n        option.value = 'foo';\n        option.innerText = 'Foo';\n        el.append(option);\n\n        await aTimeout(10);\n        await el.updateComplete;\n        expect(el.value).to.have.members(['foo']);\n        expect(new FormData(form).getAll('select')).to.have.members(['foo']);\n      });\n\n      it('Should only select the existing options if options already exists for multiple select', async () => {\n        const form = await fixture<HTMLFormElement>(\n          html` <form>\n            <sl-select name=\"select\" value=\"foo bar baz\" multiple>\n              <sl-option value=\"bar\">Bar</sl-option>\n              <sl-option value=\"baz\">Baz</sl-option>\n            </sl-select>\n          </form>`\n        );\n\n        const el = form.querySelector<SlSelect>('sl-select')!;\n        expect(el.value).to.have.members(['bar', 'baz']);\n        expect(el.value.length).to.equal(2);\n        expect(new FormData(form).getAll('select')).to.have.members(['bar', 'baz']);\n\n        const option = document.createElement('sl-option');\n        option.value = 'foo';\n        option.innerText = 'Foo';\n        el.append(option);\n\n        await aTimeout(10);\n        await el.updateComplete;\n        expect(el.value).to.have.members(['foo', 'bar', 'baz']);\n        expect(new FormData(form).getAll('select')).to.have.members(['foo', 'bar', 'baz']);\n      });\n    });\n\n    /**\n     * @see {https://github.com/shoelace-style/shoelace/issues/2254}\n     */\n    it('Should account for if `value` changed before connecting', async () => {\n      const select = await fixture<SlSelect>(html`\n        <sl-select label=\"Search By\" multiple clearable .value=${['foo', 'bar']}>\n          <sl-option value=\"foo\">Foo</sl-option>\n          <sl-option value=\"bar\">Bar</sl-option>\n        </sl-select>\n      `);\n\n      // just for safe measure.\n      await aTimeout(10);\n\n      expect(select.value).to.deep.equal(['foo', 'bar']);\n    });\n\n    /**\n     * @see {https://github.com/shoelace-style/shoelace/issues/2254}\n     */\n    it('Should still work if using the value attribute', async () => {\n      const select = await fixture<SlSelect>(html`\n        <sl-select label=\"Search By\" multiple clearable value=\"foo bar\">\n          <sl-option value=\"foo\">Foo</sl-option>\n          <sl-option value=\"bar\">Bar</sl-option>\n        </sl-select>\n      `);\n\n      // just for safe measure.\n      await aTimeout(10);\n\n      expect(select.value).to.deep.equal(['foo', 'bar']);\n\n      await clickOnElement(select);\n      await select.updateComplete;\n      await clickOnElement(select.querySelector(\"[value='foo']\")!);\n\n      await select.updateComplete;\n      await aTimeout(10);\n      expect(select.value).to.deep.equal(['bar']);\n\n      select.setAttribute('value', 'foo bar');\n      await aTimeout(10);\n      expect(select.value).to.deep.equal(['foo', 'bar']);\n    });\n  });\n\n  runFormControlBaseTests('sl-select');\n});\n"
  },
  {
    "path": "src/components/select/select.ts",
    "content": "import SlSelect from './select.component.js';\n\nexport * from './select.component.js';\nexport default SlSelect;\n\nSlSelect.define('sl-select');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-select': SlSelect;\n  }\n}\n"
  },
  {
    "path": "src/components/skeleton/skeleton.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './skeleton.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Skeletons are used to provide a visual representation of where content will eventually be drawn.\n * @documentation https://shoelace.style/components/skeleton\n * @status stable\n * @since 2.0\n *\n * @csspart base - The component's base wrapper.\n * @csspart indicator - The skeleton's indicator which is responsible for its color and animation.\n *\n * @cssproperty --border-radius - The skeleton's border radius.\n * @cssproperty --color - The color of the skeleton.\n * @cssproperty --sheen-color - The sheen color when the skeleton is in its loading state.\n */\nexport default class SlSkeleton extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  /** Determines which effect the skeleton will use. */\n  @property() effect: 'pulse' | 'sheen' | 'none' = 'none';\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          skeleton: true,\n          'skeleton--pulse': this.effect === 'pulse',\n          'skeleton--sheen': this.effect === 'sheen'\n        })}\n      >\n        <div part=\"indicator\" class=\"skeleton__indicator\"></div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/skeleton/skeleton.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --border-radius: var(--sl-border-radius-pill);\n    --color: var(--sl-color-neutral-200);\n    --sheen-color: var(--sl-color-neutral-300);\n\n    display: block;\n    position: relative;\n  }\n\n  .skeleton {\n    display: flex;\n    width: 100%;\n    height: 100%;\n    min-height: 1rem;\n  }\n\n  .skeleton__indicator {\n    flex: 1 1 auto;\n    background: var(--color);\n    border-radius: var(--border-radius);\n  }\n\n  .skeleton--sheen .skeleton__indicator {\n    background: linear-gradient(270deg, var(--sheen-color), var(--color), var(--color), var(--sheen-color));\n    background-size: 400% 100%;\n    animation: sheen 8s ease-in-out infinite;\n  }\n\n  .skeleton--pulse .skeleton__indicator {\n    animation: pulse 2s ease-in-out 0.5s infinite;\n  }\n\n  /* Forced colors mode */\n  @media (forced-colors: active) {\n    :host {\n      --color: GrayText;\n    }\n  }\n\n  @keyframes sheen {\n    0% {\n      background-position: 200% 0;\n    }\n    to {\n      background-position: -200% 0;\n    }\n  }\n\n  @keyframes pulse {\n    0% {\n      opacity: 1;\n    }\n    50% {\n      opacity: 0.4;\n    }\n    100% {\n      opacity: 1;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/skeleton/skeleton.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlSkeleton from './skeleton.js';\n\ndescribe('<sl-skeleton>', () => {\n  it('should render default skeleton', async () => {\n    const el = await fixture<SlSkeleton>(html` <sl-skeleton></sl-skeleton> `);\n\n    await expect(el).to.be.accessible();\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const indicator = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"indicator\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' skeleton ');\n    expect(indicator.getAttribute('class')).to.equal('skeleton__indicator');\n  });\n\n  it('should set pulse effect by attribute', async () => {\n    const el = await fixture<SlSkeleton>(html` <sl-skeleton effect=\"pulse\"></sl-skeleton> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' skeleton skeleton--pulse ');\n  });\n\n  it('should set sheen effect by attribute', async () => {\n    const el = await fixture<SlSkeleton>(html` <sl-skeleton effect=\"sheen\"></sl-skeleton> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' skeleton skeleton--sheen ');\n  });\n});\n"
  },
  {
    "path": "src/components/skeleton/skeleton.ts",
    "content": "import SlSkeleton from './skeleton.component.js';\n\nexport * from './skeleton.component.js';\nexport default SlSkeleton;\n\nSlSkeleton.define('sl-skeleton');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-skeleton': SlSkeleton;\n  }\n}\n"
  },
  {
    "path": "src/components/spinner/spinner.component.ts",
    "content": "import { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './spinner.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Spinners are used to show the progress of an indeterminate operation.\n * @documentation https://shoelace.style/components/spinner\n * @status stable\n * @since 2.0\n *\n * @csspart base - The component's base wrapper.\n *\n * @cssproperty --track-width - The width of the track.\n * @cssproperty --track-color - The color of the track.\n * @cssproperty --indicator-color - The color of the spinner's indicator.\n * @cssproperty --speed - The time it takes for the spinner to complete one animation cycle.\n */\nexport default class SlSpinner extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly localize = new LocalizeController(this);\n\n  render() {\n    return html`\n      <svg part=\"base\" class=\"spinner\" role=\"progressbar\" aria-label=${this.localize.term('loading')}>\n        <circle class=\"spinner__track\"></circle>\n        <circle class=\"spinner__indicator\"></circle>\n      </svg>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/spinner/spinner.styles.ts",
    "content": "import { css } from 'lit';\n\n// Resizing a spinner element using anything but font-size will break the animation because the animation uses em units.\n// Therefore, if a spinner is used in a flex container without `flex: none` applied, the spinner can grow/shrink and\n// break the animation. The use of `flex: none` on the host element prevents this by always having the spinner sized\n// according to its actual dimensions.\n\nexport default css`\n  :host {\n    --track-width: 2px;\n    --track-color: rgb(128 128 128 / 25%);\n    --indicator-color: var(--sl-color-primary-600);\n    --speed: 2s;\n\n    display: inline-flex;\n    width: 1em;\n    height: 1em;\n    flex: none;\n  }\n\n  .spinner {\n    flex: 1 1 auto;\n    height: 100%;\n    width: 100%;\n  }\n\n  .spinner__track,\n  .spinner__indicator {\n    fill: none;\n    stroke-width: var(--track-width);\n    r: calc(0.5em - var(--track-width) / 2);\n    cx: 0.5em;\n    cy: 0.5em;\n    transform-origin: 50% 50%;\n  }\n\n  .spinner__track {\n    stroke: var(--track-color);\n    transform-origin: 0% 0%;\n  }\n\n  .spinner__indicator {\n    stroke: var(--indicator-color);\n    stroke-linecap: round;\n    stroke-dasharray: 150% 75%;\n    animation: spin var(--speed) linear infinite;\n  }\n\n  @keyframes spin {\n    0% {\n      transform: rotate(0deg);\n      stroke-dasharray: 0.05em, 3em;\n    }\n\n    50% {\n      transform: rotate(450deg);\n      stroke-dasharray: 1.375em, 1.375em;\n    }\n\n    100% {\n      transform: rotate(1080deg);\n      stroke-dasharray: 0.05em, 3em;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/spinner/spinner.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport type SlSpinner from './spinner.js';\n\ndescribe('<sl-spinner>', () => {\n  describe('when provided no parameters', () => {\n    it('should pass accessibility tests', async () => {\n      const spinner = await fixture<SlSpinner>(html` <sl-spinner></sl-spinner> `);\n      await expect(spinner).to.be.accessible();\n    });\n\n    it('should have a role of \"status\".', async () => {\n      const spinner = await fixture<SlSpinner>(html` <sl-spinner></sl-spinner> `);\n      const base = spinner.shadowRoot!.querySelector('[part~=\"base\"]')!;\n      expect(base).have.attribute('role', 'progressbar');\n    });\n\n    it('should use \"transform: rotate(x)\" instead of \"rotate: x\" when animating', async () => {\n      const spinner = await fixture<SlSpinner>(html` <sl-spinner></sl-spinner> `);\n      const indicator = spinner.shadowRoot!.querySelector('.spinner__indicator')!;\n\n      //\n      // This matrix is the computed value when using `transform: rotate(x)` on the indicator. When using `rotate: x`,\n      // it will be \"none\" instead.\n      //\n      // Related: https://github.com/shoelace-style/shoelace/issues/1121\n      //\n      expect(getComputedStyle(indicator).transform).to.equal('matrix(1, 0, 0, 1, 0, 0)');\n    });\n\n    it('should have flex:none to prevent flex re-sizing', async () => {\n      const spinner = await fixture<SlSpinner>(html` <sl-spinner></sl-spinner> `);\n\n      // 0 0 auto is a compiled value for `none`\n      expect(getComputedStyle(spinner).flex).to.equal('0 0 auto');\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/spinner/spinner.ts",
    "content": "import SlSpinner from './spinner.component.js';\n\nexport * from './spinner.component.js';\nexport default SlSpinner;\n\nSlSpinner.define('sl-spinner');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-spinner': SlSpinner;\n  }\n}\n"
  },
  {
    "path": "src/components/split-panel/split-panel.component.ts",
    "content": "import { clamp } from '../../internal/math.js';\nimport { drag } from '../../internal/drag.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './split-panel.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\nexport interface SnapFunctionParams {\n  /** The position the divider has been dragged to, in pixels. */\n  pos: number;\n  /** The size of the split-panel across its primary axis, in pixels. */\n  size: number;\n  /** The snap-threshold passed to the split-panel, in pixels. May be infinity. */\n  snapThreshold: number;\n  /** Whether or not the user-agent is RTL. */\n  isRtl: boolean;\n  /** Whether or not the split panel is vertical. */\n  vertical: boolean;\n}\n\n/** Used by sl-split-panel to convert an input position into a snapped position. */\nexport type SnapFunction = (opt: SnapFunctionParams) => number | null;\n\n/** A SnapFunction which performs no snapping. */\nexport const SNAP_NONE = () => null;\n\n/**\n * @summary Split panels display two adjacent panels, allowing the user to reposition them.\n * @documentation https://shoelace.style/components/split-panel\n * @status stable\n * @since 2.0\n *\n * @event sl-reposition - Emitted when the divider's position changes.\n *\n * @slot start - Content to place in the start panel.\n * @slot end - Content to place in the end panel.\n * @slot divider - The divider. Useful for slotting in a custom icon that renders as a handle.\n *\n * @csspart start - The start panel.\n * @csspart end - The end panel.\n * @csspart panel - Targets both the start and end panels.\n * @csspart divider - The divider that separates the start and end panels.\n *\n * @cssproperty [--divider-width=4px] - The width of the visible divider.\n * @cssproperty [--divider-hit-area=12px] - The invisible region around the divider where dragging can occur. This is\n *  usually wider than the divider to facilitate easier dragging.\n * @cssproperty [--min=0] - The minimum allowed size of the primary panel.\n * @cssproperty [--max=100%] - The maximum allowed size of the primary panel.\n */\nexport default class SlSplitPanel extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private cachedPositionInPixels: number;\n  private isCollapsed = false;\n  private readonly localize = new LocalizeController(this);\n  private positionBeforeCollapsing = 0;\n  private resizeObserver: ResizeObserver;\n  private size: number;\n\n  @query('.divider') divider: HTMLElement;\n\n  /**\n   * The current position of the divider from the primary panel's edge as a percentage 0-100. Defaults to 50% of the\n   * container's initial size.\n   */\n  @property({ type: Number, reflect: true }) position = 50;\n\n  /** The current position of the divider from the primary panel's edge in pixels. */\n  @property({ attribute: 'position-in-pixels', type: Number }) positionInPixels: number;\n\n  /** Draws the split panel in a vertical orientation with the start and end panels stacked. */\n  @property({ type: Boolean, reflect: true }) vertical = false;\n\n  /** Disables resizing. Note that the position may still change as a result of resizing the host element. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * If no primary panel is designated, both panels will resize proportionally when the host element is resized. If a\n   * primary panel is designated, it will maintain its size and the other panel will grow or shrink as needed when the\n   * host element is resized.\n   */\n  @property() primary?: 'start' | 'end';\n\n  // Returned when the property is queried, so that string 'snap's are preserved.\n  private snapValue: string | SnapFunction = '';\n  // Actually used for computing snap points. All string snaps are converted via `toSnapFunction`.\n  private snapFunction: SnapFunction = SNAP_NONE;\n\n  /**\n   * Converts a string containing either a series of fixed/repeated snap points (e.g. \"repeat(20%)\", \"100px 200px 800px\", or \"10% 50% repeat(10px)\") into a SnapFunction. `SnapFunction`s take in a `SnapFunctionOpts` and return the position that the split panel should snap to.\n   *\n   * @param snap - The snap string.\n   * @returns a `SnapFunction` representing the snap string's logic.\n   */\n  private toSnapFunction(snap: string): SnapFunction {\n    const snapPoints = snap.split(' ');\n\n    return ({ pos, size, snapThreshold, isRtl, vertical }) => {\n      let newPos = pos;\n      let minDistance = Number.POSITIVE_INFINITY;\n\n      snapPoints.forEach(value => {\n        let snapPoint: number;\n\n        if (value.startsWith('repeat(')) {\n          const repeatVal = snap.substring('repeat('.length, snap.length - 1);\n          const isPercent = repeatVal.endsWith('%');\n          const repeatNum = Number.parseFloat(repeatVal);\n          const snapIntervalPx = isPercent ? size * (repeatNum / 100) : repeatNum;\n          snapPoint = Math.round((isRtl && !vertical ? size - pos : pos) / snapIntervalPx) * snapIntervalPx;\n        } else if (value.endsWith('%')) {\n          snapPoint = size * (Number.parseFloat(value) / 100);\n        } else {\n          snapPoint = Number.parseFloat(value);\n        }\n\n        if (isRtl && !vertical) {\n          snapPoint = size - snapPoint;\n        }\n\n        const distance = Math.abs(pos - snapPoint);\n\n        if (distance <= snapThreshold && distance < minDistance) {\n          newPos = snapPoint;\n          minDistance = distance;\n        }\n      });\n\n      return newPos;\n    };\n  }\n\n  /**\n   * Either one or more space-separated values at which the divider should snap, in pixels, percentages, or repeat expressions e.g. `'100px 50% 500px' or `repeat(50%) 10px`,\n   * or a function which takes in a `SnapFunctionParams`, and returns a position to snap to, e.g. `({ pos }) => Math.round(pos / 8) * 8`.\n   */\n  @property({ reflect: true })\n  set snap(snap: string | SnapFunction | null | undefined) {\n    this.snapValue = snap ?? '';\n    if (snap) {\n      this.snapFunction = typeof snap === 'string' ? this.toSnapFunction(snap) : snap;\n    } else {\n      this.snapFunction = SNAP_NONE;\n    }\n  }\n\n  get snap(): string | SnapFunction {\n    return this.snapValue;\n  }\n\n  /** How close the divider must be to a snap point until snapping occurs. */\n  @property({ type: Number, attribute: 'snap-threshold' }) snapThreshold = 12;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.resizeObserver = new ResizeObserver(entries => this.handleResize(entries));\n    this.updateComplete.then(() => this.resizeObserver.observe(this));\n\n    this.detectSize();\n    this.cachedPositionInPixels = this.percentageToPixels(this.position);\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.resizeObserver?.unobserve(this);\n  }\n\n  private detectSize() {\n    const { width, height } = this.getBoundingClientRect();\n    this.size = this.vertical ? height : width;\n  }\n\n  private percentageToPixels(value: number) {\n    return this.size * (value / 100);\n  }\n\n  private pixelsToPercentage(value: number) {\n    return (value / this.size) * 100;\n  }\n\n  private handleDrag(event: PointerEvent) {\n    const isRtl = this.localize.dir() === 'rtl';\n\n    if (this.disabled) {\n      return;\n    }\n\n    // Prevent text selection when dragging\n    if (event.cancelable) {\n      event.preventDefault();\n    }\n\n    drag(this, {\n      onMove: (x, y) => {\n        let newPositionInPixels = this.vertical ? y : x;\n\n        // Flip for end panels\n        if (this.primary === 'end') {\n          newPositionInPixels = this.size - newPositionInPixels;\n        }\n\n        // Check snap points\n        newPositionInPixels =\n          this.snapFunction({\n            pos: newPositionInPixels,\n            size: this.size,\n            snapThreshold: this.snapThreshold,\n            isRtl: isRtl,\n            vertical: this.vertical\n          }) ?? newPositionInPixels;\n\n        this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);\n      },\n      initialEvent: event\n    });\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    if (this.disabled) {\n      return;\n    }\n\n    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter'].includes(event.key)) {\n      let newPosition = this.position;\n      const incr = (event.shiftKey ? 10 : 1) * (this.primary === 'end' ? -1 : 1);\n\n      event.preventDefault();\n\n      if ((event.key === 'ArrowLeft' && !this.vertical) || (event.key === 'ArrowUp' && this.vertical)) {\n        newPosition -= incr;\n      }\n\n      if ((event.key === 'ArrowRight' && !this.vertical) || (event.key === 'ArrowDown' && this.vertical)) {\n        newPosition += incr;\n      }\n\n      if (event.key === 'Home') {\n        newPosition = this.primary === 'end' ? 100 : 0;\n      }\n\n      if (event.key === 'End') {\n        newPosition = this.primary === 'end' ? 0 : 100;\n      }\n\n      // Collapse/expand the primary panel when enter is pressed\n      if (event.key === 'Enter') {\n        if (this.isCollapsed) {\n          newPosition = this.positionBeforeCollapsing;\n          this.isCollapsed = false;\n        } else {\n          const positionBeforeCollapsing = this.position;\n\n          newPosition = 0;\n\n          // Wait for position to update before setting the collapsed state\n          requestAnimationFrame(() => {\n            this.isCollapsed = true;\n            this.positionBeforeCollapsing = positionBeforeCollapsing;\n          });\n        }\n      }\n\n      this.position = clamp(newPosition, 0, 100);\n    }\n  }\n\n  private handleResize(entries: ResizeObserverEntry[]) {\n    const { width, height } = entries[0].contentRect;\n    this.size = this.vertical ? height : width;\n\n    // There's some weird logic that gets `this.cachedPositionInPixels = NaN` or `this.position === Infinity` when\n    // a split-panel goes from `display: none;` to showing.\n    if (isNaN(this.cachedPositionInPixels) || this.position === Infinity) {\n      this.cachedPositionInPixels = Number(this.getAttribute('position-in-pixels'));\n      this.positionInPixels = Number(this.getAttribute('position-in-pixels'));\n      this.position = this.pixelsToPercentage(this.positionInPixels);\n    }\n\n    // Resize when a primary panel is set\n    if (this.primary) {\n      this.position = this.pixelsToPercentage(this.cachedPositionInPixels);\n    }\n  }\n\n  @watch('position')\n  handlePositionChange() {\n    this.cachedPositionInPixels = this.percentageToPixels(this.position);\n    this.isCollapsed = false;\n    this.positionBeforeCollapsing = 0;\n    this.positionInPixels = this.percentageToPixels(this.position);\n    this.emit('sl-reposition');\n  }\n\n  @watch('positionInPixels')\n  handlePositionInPixelsChange() {\n    this.position = this.pixelsToPercentage(this.positionInPixels);\n  }\n\n  @watch('vertical')\n  handleVerticalChange() {\n    this.detectSize();\n  }\n\n  render() {\n    const gridTemplate = this.vertical ? 'gridTemplateRows' : 'gridTemplateColumns';\n    const gridTemplateAlt = this.vertical ? 'gridTemplateColumns' : 'gridTemplateRows';\n    const isRtl = this.localize.dir() === 'rtl';\n    const primary = `\n      clamp(\n        0%,\n        clamp(\n          var(--min),\n          ${this.position}% - var(--divider-width) / 2,\n          var(--max)\n        ),\n        calc(100% - var(--divider-width))\n      )\n    `;\n    const secondary = 'auto';\n\n    if (this.primary === 'end') {\n      if (isRtl && !this.vertical) {\n        this.style[gridTemplate] = `${primary} var(--divider-width) ${secondary}`;\n      } else {\n        this.style[gridTemplate] = `${secondary} var(--divider-width) ${primary}`;\n      }\n    } else {\n      if (isRtl && !this.vertical) {\n        this.style[gridTemplate] = `${secondary} var(--divider-width) ${primary}`;\n      } else {\n        this.style[gridTemplate] = `${primary} var(--divider-width) ${secondary}`;\n      }\n    }\n\n    // Unset the alt grid template property\n    this.style[gridTemplateAlt] = '';\n\n    return html`\n      <slot name=\"start\" part=\"panel start\" class=\"start\"></slot>\n\n      <div\n        part=\"divider\"\n        class=\"divider\"\n        tabindex=${ifDefined(this.disabled ? undefined : '0')}\n        role=\"separator\"\n        aria-valuenow=${this.position}\n        aria-valuemin=\"0\"\n        aria-valuemax=\"100\"\n        aria-label=${this.localize.term('resize')}\n        @keydown=${this.handleKeyDown}\n        @mousedown=${this.handleDrag}\n        @touchstart=${this.handleDrag}\n      >\n        <slot name=\"divider\"></slot>\n      </div>\n\n      <slot name=\"end\" part=\"panel end\" class=\"end\"></slot>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-split-panel': SlSplitPanel;\n  }\n}\n"
  },
  {
    "path": "src/components/split-panel/split-panel.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --divider-width: 4px;\n    --divider-hit-area: 12px;\n    --min: 0%;\n    --max: 100%;\n\n    display: grid;\n  }\n\n  .start,\n  .end {\n    overflow: hidden;\n  }\n\n  .divider {\n    flex: 0 0 var(--divider-width);\n    display: flex;\n    position: relative;\n    align-items: center;\n    justify-content: center;\n    background-color: var(--sl-color-neutral-200);\n    color: var(--sl-color-neutral-900);\n    z-index: 1;\n  }\n\n  .divider:focus {\n    outline: none;\n  }\n\n  :host(:not([disabled])) .divider:focus-visible {\n    background-color: var(--sl-color-primary-600);\n    color: var(--sl-color-neutral-0);\n  }\n\n  :host([disabled]) .divider {\n    cursor: not-allowed;\n  }\n\n  /* Horizontal */\n  :host(:not([vertical], [disabled])) .divider {\n    cursor: col-resize;\n  }\n\n  :host(:not([vertical])) .divider::after {\n    display: flex;\n    content: '';\n    position: absolute;\n    height: 100%;\n    left: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2);\n    width: var(--divider-hit-area);\n  }\n\n  /* Vertical */\n  :host([vertical]) {\n    flex-direction: column;\n  }\n\n  :host([vertical]:not([disabled])) .divider {\n    cursor: row-resize;\n  }\n\n  :host([vertical]) .divider::after {\n    content: '';\n    position: absolute;\n    width: 100%;\n    top: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2);\n    height: var(--divider-hit-area);\n  }\n\n  @media (forced-colors: active) {\n    .divider {\n      outline: solid 1px transparent;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/split-panel/split-panel.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { dragElement } from '../../internal/test.js';\nimport { expect, fixture, html, oneEvent } from '@open-wc/testing';\nimport { queryByTestId } from '../../internal/test/data-testid-helpers.js';\nimport { resetMouse } from '@web/test-runner-commands';\nimport type SlSplitPanel from './split-panel.js';\n\nconst DIVIDER_WIDTH_IN_PX = 4;\n\nconst getPanel = (splitPanel: SlSplitPanel, testid: string): HTMLElement => {\n  const startPanel = queryByTestId<HTMLElement>(splitPanel, testid);\n  expect(startPanel).not.to.be.null;\n  return startPanel!;\n};\n\nconst getPanelWidth = (splitPanel: SlSplitPanel, testid: string) => {\n  const panel = getPanel(splitPanel, testid);\n  const { width } = panel.getBoundingClientRect();\n  return width;\n};\n\nconst getPanelHeight = (splitPanel: SlSplitPanel, testid: string) => {\n  const panel = getPanel(splitPanel, testid);\n  const { height } = panel.getBoundingClientRect();\n  return height;\n};\n\nconst getDivider = (splitPanel: SlSplitPanel): Element => {\n  const divider = splitPanel.shadowRoot?.querySelector('[part=\"divider\"]');\n  expect(divider).not.to.be.null;\n  return divider!;\n};\n\ndescribe('<sl-split-panel>', () => {\n  afterEach(async () => {\n    await resetMouse();\n  });\n\n  it('should render a component', async () => {\n    const splitPanel = await fixture(html` <sl-split-panel></sl-split-panel> `);\n\n    expect(splitPanel).to.exist;\n  });\n\n  it('should be accessible', async () => {\n    const splitPanel = await fixture(\n      html`<sl-split-panel>\n        <div slot=\"start\">Start</div>\n        <div slot=\"end\">End</div>\n      </sl-split-panel>`\n    );\n\n    await expect(splitPanel).to.be.accessible();\n  });\n\n  it('should show both panels', async () => {\n    const splitPanel = await fixture(\n      html`<sl-split-panel>\n        <div slot=\"start\">Start</div>\n        <div slot=\"end\">End</div>\n      </sl-split-panel>`\n    );\n\n    expect(splitPanel).to.contain.text('Start');\n    expect(splitPanel).to.contain.text('End');\n  });\n\n  describe('panel sizing horizontal', () => {\n    it('has two evenly sized panels by default', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel>\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      const startPanelWidth = getPanelWidth(splitPanel, 'start-panel');\n      const endPanelWidth = getPanelWidth(splitPanel, 'end-panel');\n\n      expect(startPanelWidth).to.be.equal(endPanelWidth);\n    });\n\n    it('changes the sizing of the panels based on the position attribute', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel position=\"25\">\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      const startPanelWidth = getPanelWidth(splitPanel, 'start-panel');\n      const endPanelWidth = getPanelWidth(splitPanel, 'end-panel');\n\n      expect(startPanelWidth * 3).to.be.equal(endPanelWidth - DIVIDER_WIDTH_IN_PX);\n    });\n\n    it('updates the position in pixels to the correct result', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel position=\"25\">\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      splitPanel.position = 10;\n\n      const startPanelWidth = getPanelWidth(splitPanel, 'start-panel');\n\n      expect(startPanelWidth).to.be.equal(splitPanel.positionInPixels - DIVIDER_WIDTH_IN_PX / 2);\n    });\n\n    it('emits the sl-reposition\tevent on position change', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel>\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const repositionPromise = oneEvent(splitPanel, 'sl-reposition');\n      splitPanel.position = 10;\n      return repositionPromise;\n    });\n\n    it('can be resized using the mouse', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel>\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels - 30);\n    });\n\n    it('cannot be resized if disabled', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel disabled>\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels);\n    });\n\n    it('snaps to predefined positions', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel>\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n      splitPanel.snap = `${positionInPixels - 40}px`;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels - 40);\n    });\n  });\n\n  describe('panel sizing vertical', () => {\n    it('has two evenly sized panels by default', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel vertical style=\"height: 400px;\">\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      const startPanelHeight = getPanelHeight(splitPanel, 'start-panel');\n      const endPanelHeight = getPanelHeight(splitPanel, 'end-panel');\n\n      expect(startPanelHeight).to.be.equal(endPanelHeight);\n    });\n\n    it('changes the sizing of the panels based on the position attribute', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel position=\"25\" vertical style=\"height: 400px;\">\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      const startPanelHeight = getPanelHeight(splitPanel, 'start-panel');\n      const endPanelHeight = getPanelHeight(splitPanel, 'end-panel');\n\n      expect(startPanelHeight * 3).to.be.equal(endPanelHeight - DIVIDER_WIDTH_IN_PX);\n    });\n\n    it('updates the position in pixels to the correct result', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel position=\"25\" vertical style=\"height: 400px;\">\n          <div slot=\"start\" data-testid=\"start-panel\">Start</div>\n          <div slot=\"end\" data-testid=\"end-panel\">End</div>\n        </sl-split-panel>`\n      );\n\n      splitPanel.position = 10;\n\n      const startPanelHeight = getPanelHeight(splitPanel, 'start-panel');\n\n      expect(startPanelHeight).to.be.equal(splitPanel.positionInPixels - DIVIDER_WIDTH_IN_PX / 2);\n    });\n\n    it('emits the sl-reposition\tevent on position change ', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel vertical style=\"height: 400px;\">\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const repositionPromise = oneEvent(splitPanel, 'sl-reposition');\n      splitPanel.position = 10;\n      return repositionPromise;\n    });\n\n    it('can be resized using the mouse ', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel vertical style=\"height: 400px;\">\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, 0, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels - 30);\n    });\n\n    it('cannot be resized if disabled', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel disabled vertical style=\"height: 400px;\">\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, 0, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels);\n    });\n\n    it('snaps to predefined positions', async () => {\n      const splitPanel = await fixture<SlSplitPanel>(\n        html`<sl-split-panel vertical style=\"height: 400px;\">\n          <div slot=\"start\">Start</div>\n          <div slot=\"end\">End</div>\n        </sl-split-panel>`\n      );\n\n      const positionInPixels = splitPanel.positionInPixels;\n      splitPanel.snap = `${positionInPixels - 40}px`;\n\n      const divider = getDivider(splitPanel);\n\n      await dragElement(divider, 0, -30);\n\n      const positionInPixelsAfterDrag = splitPanel.positionInPixels;\n      expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels - 40);\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/split-panel/split-panel.ts",
    "content": "import SlSplitPanel from './split-panel.component.js';\n\nexport * from './split-panel.component.js';\nexport default SlSplitPanel;\n\nSlSplitPanel.define('sl-split-panel');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-split-panel': SlSplitPanel;\n  }\n}\n"
  },
  {
    "path": "src/components/switch/switch.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './switch.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Switches allow the user to toggle an option on or off.\n * @documentation https://shoelace.style/components/switch\n * @status stable\n * @since 2.0\n *\n * @slot - The switch's label.\n * @slot help-text - Text that describes how to use the switch. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-change - Emitted when the control's checked state changes.\n * @event sl-input - Emitted when the control receives input.\n * @event sl-focus - Emitted when the control gains focus.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart base - The component's base wrapper.\n * @csspart control - The control that houses the switch's thumb.\n * @csspart thumb - The switch's thumb.\n * @csspart label - The switch's label.\n * @csspart form-control-help-text - The help text's wrapper.\n *\n * @cssproperty --width - The width of the switch.\n * @cssproperty --height - The height of the switch.\n * @cssproperty --thumb-size - The size of the thumb.\n */\nexport default class SlSwitch extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n\n  private readonly formControlController = new FormControlController(this, {\n    value: (control: SlSwitch) => (control.checked ? control.value || 'on' : undefined),\n    defaultValue: (control: SlSwitch) => control.defaultChecked,\n    setValue: (control: SlSwitch, checked: boolean) => (control.checked = checked)\n  });\n  private readonly hasSlotController = new HasSlotController(this, 'help-text');\n\n  @query('input[type=\"checkbox\"]') input: HTMLInputElement;\n\n  @state() private hasFocus = false;\n  @property() title = ''; // make reactive to pass through\n\n  /** The name of the switch, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** The current value of the switch, submitted as a name/value pair with form data. */\n  @property() value: string;\n\n  /** The switch's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Disables the switch. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Draws the switch in a checked state. */\n  @property({ type: Boolean, reflect: true }) checked = false;\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue('checked') defaultChecked = false;\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Makes the switch a required field. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** The switch's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  firstUpdated() {\n    this.formControlController.updateValidity();\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleInput() {\n    this.emit('sl-input');\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private handleClick() {\n    this.checked = !this.checked;\n    this.emit('sl-change');\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    if (event.key === 'ArrowLeft') {\n      event.preventDefault();\n      this.checked = false;\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n\n    if (event.key === 'ArrowRight') {\n      event.preventDefault();\n      this.checked = true;\n      this.emit('sl-change');\n      this.emit('sl-input');\n    }\n  }\n\n  @watch('checked', { waitUntilFirstUpdate: true })\n  handleCheckedChange() {\n    this.input.checked = this.checked; // force a sync update\n    this.formControlController.updateValidity();\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Disabled form controls are always valid\n    this.formControlController.setValidity(true);\n  }\n\n  /** Simulates a click on the switch. */\n  click() {\n    this.input.click();\n  }\n\n  /** Sets focus on the switch. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the switch. */\n  blur() {\n    this.input.blur();\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.input.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n\n    return html`\n      <div\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          part=\"base\"\n          class=${classMap({\n            switch: true,\n            'switch--checked': this.checked,\n            'switch--disabled': this.disabled,\n            'switch--focused': this.hasFocus,\n            'switch--small': this.size === 'small',\n            'switch--medium': this.size === 'medium',\n            'switch--large': this.size === 'large'\n          })}\n        >\n          <input\n            class=\"switch__input\"\n            type=\"checkbox\"\n            title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n            name=${this.name}\n            value=${ifDefined(this.value)}\n            .checked=${live(this.checked)}\n            .disabled=${this.disabled}\n            .required=${this.required}\n            role=\"switch\"\n            aria-checked=${this.checked ? 'true' : 'false'}\n            aria-describedby=\"help-text\"\n            @click=${this.handleClick}\n            @input=${this.handleInput}\n            @invalid=${this.handleInvalid}\n            @blur=${this.handleBlur}\n            @focus=${this.handleFocus}\n            @keydown=${this.handleKeyDown}\n          />\n\n          <span part=\"control\" class=\"switch__control\">\n            <span part=\"thumb\" class=\"switch__thumb\"></span>\n          </span>\n\n          <div part=\"label\" class=\"switch__label\">\n            <slot></slot>\n          </div>\n        </label>\n\n        <div\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n          class=\"form-control__help-text\"\n          id=\"help-text\"\n          part=\"form-control-help-text\"\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-switch': SlSwitch;\n  }\n}\n"
  },
  {
    "path": "src/components/switch/switch.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  :host([size='small']) {\n    --height: var(--sl-toggle-size-small);\n    --thumb-size: calc(var(--sl-toggle-size-small) + 4px);\n    --width: calc(var(--height) * 2);\n\n    font-size: var(--sl-input-font-size-small);\n  }\n\n  :host([size='medium']) {\n    --height: var(--sl-toggle-size-medium);\n    --thumb-size: calc(var(--sl-toggle-size-medium) + 4px);\n    --width: calc(var(--height) * 2);\n\n    font-size: var(--sl-input-font-size-medium);\n  }\n\n  :host([size='large']) {\n    --height: var(--sl-toggle-size-large);\n    --thumb-size: calc(var(--sl-toggle-size-large) + 4px);\n    --width: calc(var(--height) * 2);\n\n    font-size: var(--sl-input-font-size-large);\n  }\n\n  .switch {\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    font-family: var(--sl-input-font-family);\n    font-size: inherit;\n    font-weight: var(--sl-input-font-weight);\n    color: var(--sl-input-label-color);\n    vertical-align: middle;\n    cursor: pointer;\n  }\n\n  .switch__control {\n    flex: 0 0 auto;\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    width: var(--width);\n    height: var(--height);\n    background-color: var(--sl-color-neutral-400);\n    border: solid var(--sl-input-border-width) var(--sl-color-neutral-400);\n    border-radius: var(--height);\n    transition:\n      var(--sl-transition-fast) border-color,\n      var(--sl-transition-fast) background-color;\n  }\n\n  .switch__control .switch__thumb {\n    width: var(--thumb-size);\n    height: var(--thumb-size);\n    background-color: var(--sl-color-neutral-0);\n    border-radius: 50%;\n    border: solid var(--sl-input-border-width) var(--sl-color-neutral-400);\n    translate: calc((var(--width) - var(--height)) / -2);\n    transition:\n      var(--sl-transition-fast) translate ease,\n      var(--sl-transition-fast) background-color,\n      var(--sl-transition-fast) border-color,\n      var(--sl-transition-fast) box-shadow;\n  }\n\n  .switch__input {\n    position: absolute;\n    opacity: 0;\n    padding: 0;\n    margin: 0;\n    pointer-events: none;\n  }\n\n  /* Hover */\n  .switch:not(.switch--checked):not(.switch--disabled) .switch__control:hover {\n    background-color: var(--sl-color-neutral-400);\n    border-color: var(--sl-color-neutral-400);\n  }\n\n  .switch:not(.switch--checked):not(.switch--disabled) .switch__control:hover .switch__thumb {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-neutral-400);\n  }\n\n  /* Focus */\n  .switch:not(.switch--checked):not(.switch--disabled) .switch__input:focus-visible ~ .switch__control {\n    background-color: var(--sl-color-neutral-400);\n    border-color: var(--sl-color-neutral-400);\n  }\n\n  .switch:not(.switch--checked):not(.switch--disabled) .switch__input:focus-visible ~ .switch__control .switch__thumb {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-primary-600);\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  /* Checked */\n  .switch--checked .switch__control {\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n  }\n\n  .switch--checked .switch__control .switch__thumb {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-primary-600);\n    translate: calc((var(--width) - var(--height)) / 2);\n  }\n\n  /* Checked + hover */\n  .switch.switch--checked:not(.switch--disabled) .switch__control:hover {\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n  }\n\n  .switch.switch--checked:not(.switch--disabled) .switch__control:hover .switch__thumb {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-primary-600);\n  }\n\n  /* Checked + focus */\n  .switch.switch--checked:not(.switch--disabled) .switch__input:focus-visible ~ .switch__control {\n    background-color: var(--sl-color-primary-600);\n    border-color: var(--sl-color-primary-600);\n  }\n\n  .switch.switch--checked:not(.switch--disabled) .switch__input:focus-visible ~ .switch__control .switch__thumb {\n    background-color: var(--sl-color-neutral-0);\n    border-color: var(--sl-color-primary-600);\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  /* Disabled */\n  .switch--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .switch__label {\n    display: inline-block;\n    line-height: var(--height);\n    margin-inline-start: 0.5em;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  :host([required]) .switch__label::after {\n    content: var(--sl-input-required-content);\n    color: var(--sl-input-required-content-color);\n    margin-inline-start: var(--sl-input-required-content-offset);\n  }\n\n  @media (forced-colors: active) {\n    .switch.switch--checked:not(.switch--disabled) .switch__control:hover .switch__thumb,\n    .switch--checked .switch__control .switch__thumb {\n      background-color: ButtonText;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/switch/switch.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlSwitch from './switch.js';\n\ndescribe('<sl-switch>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch>Switch</sl-switch> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n\n    expect(el.name).to.equal('');\n    expect(el.value).to.be.undefined;\n    expect(el.title).to.equal('');\n    expect(el.disabled).to.be.false;\n    expect(el.required).to.be.false;\n    expect(el.checked).to.be.false;\n    expect(el.defaultChecked).to.be.false;\n    expect(el.helpText).to.equal('');\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch title=\"Test\"></sl-switch> `);\n    const input = el.shadowRoot!.querySelector('input')!;\n\n    expect(input.title).to.equal('Test');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch disabled></sl-switch> `);\n    const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;\n\n    expect(input.disabled).to.be.true;\n  });\n\n  it('should be valid by default', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n\n    expect(el.checkValidity()).to.be.true;\n  });\n\n  it('should emit sl-change and sl-input when clicked', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.click();\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.true;\n  });\n\n  it('should emit sl-change when toggled with spacebar', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.focus();\n    await sendKeys({ press: ' ' });\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.true;\n  });\n\n  it('should emit sl-change and sl-input when toggled with the right arrow', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.focus();\n    await sendKeys({ press: 'ArrowRight' });\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.true;\n  });\n\n  it('should emit sl-change and sl-input when toggled with the left arrow', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch checked></sl-switch> `);\n    const changeHandler = sinon.spy();\n    const inputHandler = sinon.spy();\n\n    el.addEventListener('sl-change', changeHandler);\n    el.addEventListener('sl-input', inputHandler);\n    el.focus();\n    await sendKeys({ press: 'ArrowLeft' });\n    await el.updateComplete;\n\n    expect(changeHandler).to.have.been.calledOnce;\n    expect(inputHandler).to.have.been.calledOnce;\n    expect(el.checked).to.be.false;\n  });\n\n  it('should not emit sl-change or sl-input when checked is set by JavaScript', async () => {\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n    el.addEventListener('sl-change', () => expect.fail('sl-change incorrectly emitted'));\n    el.addEventListener('sl-input', () => expect.fail('sl-change incorrectly emitted'));\n    el.checked = true;\n    await el.updateComplete;\n    el.checked = false;\n    await el.updateComplete;\n  });\n\n  it('should hide the native input with the correct positioning to scroll correctly when contained in an overflow', async () => {\n    //\n    // See: https://github.com/shoelace-style/shoelace/issues/1169\n    //\n    const el = await fixture<SlSwitch>(html` <sl-switch></sl-switch> `);\n    const label = el.shadowRoot!.querySelector('.switch')!;\n    const input = el.shadowRoot!.querySelector('.switch__input')!;\n\n    const labelPosition = getComputedStyle(label).position;\n    const inputPosition = getComputedStyle(input).position;\n\n    expect(labelPosition).to.equal('relative');\n    expect(inputPosition).to.equal('absolute');\n  });\n\n  describe('when submitting a form', () => {\n    it('should submit the correct value when a value is provided', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-switch name=\"a\" value=\"1\" checked></sl-switch>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => {\n        formData = new FormData(form);\n        event.preventDefault();\n      });\n      let formData: FormData;\n\n      form.addEventListener('submit', submitHandler);\n      button.click();\n\n      await waitUntil(() => submitHandler.calledOnce);\n\n      expect(formData!.get('a')).to.equal('1');\n    });\n\n    it('should submit \"on\" when no value is provided', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-switch name=\"a\" checked></sl-switch>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => {\n        formData = new FormData(form);\n        event.preventDefault();\n      });\n      let formData: FormData;\n\n      form.addEventListener('submit', submitHandler);\n      button.click();\n\n      await waitUntil(() => submitHandler.calledOnce);\n\n      expect(formData!.get('a')).to.equal('on');\n    });\n\n    it('should show a constraint validation error when setCustomValidity() is called', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-switch name=\"a\" value=\"1\" checked></sl-switch>\n          <sl-button type=\"submit\">Submit</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const slSwitch = form.querySelector('sl-switch')!;\n      const submitHandler = sinon.spy((event: SubmitEvent) => event.preventDefault());\n\n      // Submitting the form after setting custom validity should not trigger the handler\n      slSwitch.setCustomValidity('Invalid selection');\n      form.addEventListener('submit', submitHandler);\n      button.click();\n      await aTimeout(100);\n\n      expect(submitHandler).to.not.have.been.called;\n    });\n\n    it('should be invalid when required and unchecked', async () => {\n      const slSwitch = await fixture<HTMLFormElement>(html` <sl-switch required></sl-switch> `);\n      expect(slSwitch.checkValidity()).to.be.false;\n    });\n\n    it('should be valid when required and checked', async () => {\n      const slSwitch = await fixture<HTMLFormElement>(html` <sl-switch required checked></sl-switch> `);\n      expect(slSwitch.checkValidity()).to.be.true;\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-switch form=\"f\" name=\"a\" value=\"1\" checked></sl-switch>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('1');\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-switch required></sl-switch></form> `);\n      const slSwitch = el.querySelector<SlSwitch>('sl-switch')!;\n\n      expect(slSwitch.hasAttribute('data-required')).to.be.true;\n      expect(slSwitch.hasAttribute('data-optional')).to.be.false;\n      expect(slSwitch.hasAttribute('data-invalid')).to.be.true;\n      expect(slSwitch.hasAttribute('data-valid')).to.be.false;\n      expect(slSwitch.hasAttribute('data-user-invalid')).to.be.false;\n      expect(slSwitch.hasAttribute('data-user-valid')).to.be.false;\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-switch name=\"a\" value=\"1\" checked></sl-switch>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const switchEl = form.querySelector('sl-switch')!;\n      switchEl.checked = false;\n\n      await switchEl.updateComplete;\n      setTimeout(() => button.click());\n\n      await oneEvent(form, 'reset');\n      await switchEl.updateComplete;\n\n      expect(switchEl.checked).to.true;\n\n      switchEl.defaultChecked = false;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await switchEl.updateComplete;\n\n      expect(switchEl.checked).to.false;\n    });\n  });\n\n  it('should not jump the page to the bottom when focusing a switch at the bottom of an element with overflow: auto;', async () => {\n    // https://github.com/shoelace-style/shoelace/issues/1169\n    const el = await fixture<HTMLDivElement>(html`\n      <div style=\"display: flex; flex-direction: column; overflow: auto; max-height: 400px;\">\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n        <sl-switch>Switch</sl-switch>\n      </div>\n      ;\n    `);\n\n    const switches = el.querySelectorAll<SlSwitch>('sl-switch');\n    const lastSwitch = switches[switches.length - 1];\n\n    expect(window.scrollY).to.equal(0);\n    // Without these 2 timeouts, tests will pass unexpectedly in Safari.\n    await aTimeout(10);\n    lastSwitch.focus();\n    await aTimeout(10);\n    expect(window.scrollY).to.equal(0);\n  });\n\n  runFormControlBaseTests('sl-switch');\n});\n"
  },
  {
    "path": "src/components/switch/switch.ts",
    "content": "import SlSwitch from './switch.component.js';\n\nexport * from './switch.component.js';\nexport default SlSwitch;\n\nSlSwitch.define('sl-switch');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-switch': SlSwitch;\n  }\n}\n"
  },
  {
    "path": "src/components/tab/tab.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport styles from './tab.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\nlet id = 0;\n\n/**\n * @summary Tabs are used inside [tab groups](/components/tab-group) to represent and activate [tab panels](/components/tab-panel).\n * @documentation https://shoelace.style/components/tab\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - The tab's label.\n *\n * @event sl-close - Emitted when the tab is closable and the close button is activated.\n *\n * @csspart base - The component's base wrapper.\n * @csspart close-button - The close button, an `<sl-icon-button>`.\n * @csspart close-button__base - The close button's exported `base` part.\n */\nexport default class SlTab extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon-button': SlIconButton };\n\n  private readonly localize = new LocalizeController(this);\n\n  private readonly attrId = ++id;\n  private readonly componentId = `sl-tab-${this.attrId}`;\n\n  @query('.tab') tab: HTMLElement;\n\n  /** The name of the tab panel this tab is associated with. The panel must be located in the same tab group. */\n  @property({ reflect: true }) panel = '';\n\n  /** Draws the tab in an active state. */\n  @property({ type: Boolean, reflect: true }) active = false;\n\n  /** Makes the tab closable and shows a close button. */\n  @property({ type: Boolean, reflect: true }) closable = false;\n\n  /** Disables the tab and prevents selection. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /**\n   * @internal\n   * Need to wrap in a `@property()` otherwise CustomElement throws a \"The result must not have attributes\" runtime error.\n   */\n  @property({ type: Number, reflect: true }) tabIndex = 0;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.setAttribute('role', 'tab');\n  }\n\n  private handleCloseClick(event: Event) {\n    event.stopPropagation();\n    this.emit('sl-close');\n  }\n\n  @watch('active')\n  handleActiveChange() {\n    this.setAttribute('aria-selected', this.active ? 'true' : 'false');\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n\n    if (this.disabled && !this.active) {\n      this.tabIndex = -1;\n    } else {\n      this.tabIndex = 0;\n    }\n  }\n\n  render() {\n    // If the user didn't provide an ID, we'll set one so we can link tabs and tab panels with aria labels\n    this.id = this.id.length > 0 ? this.id : this.componentId;\n\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          tab: true,\n          'tab--active': this.active,\n          'tab--closable': this.closable,\n          'tab--disabled': this.disabled\n        })}\n      >\n        <slot></slot>\n        ${this.closable\n          ? html`\n              <sl-icon-button\n                part=\"close-button\"\n                exportparts=\"base:close-button__base\"\n                name=\"x-lg\"\n                library=\"system\"\n                label=${this.localize.term('close')}\n                class=\"tab__close-button\"\n                @click=${this.handleCloseClick}\n                tabindex=\"-1\"\n              ></sl-icon-button>\n            `\n          : ''}\n      </div>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tab': SlTab;\n  }\n}\n"
  },
  {
    "path": "src/components/tab/tab.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  .tab {\n    display: inline-flex;\n    align-items: center;\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-small);\n    font-weight: var(--sl-font-weight-semibold);\n    border-radius: var(--sl-border-radius-medium);\n    color: var(--sl-color-neutral-600);\n    padding: var(--sl-spacing-medium) var(--sl-spacing-large);\n    white-space: nowrap;\n    user-select: none;\n    -webkit-user-select: none;\n    cursor: pointer;\n    transition:\n      var(--transition-speed) box-shadow,\n      var(--transition-speed) color;\n  }\n\n  .tab:hover:not(.tab--disabled) {\n    color: var(--sl-color-primary-600);\n  }\n\n  :host(:focus) {\n    outline: transparent;\n  }\n\n  :host(:focus-visible) {\n    color: var(--sl-color-primary-600);\n    outline: var(--sl-focus-ring);\n    outline-offset: calc(-1 * var(--sl-focus-ring-width) - var(--sl-focus-ring-offset));\n  }\n\n  .tab.tab--active:not(.tab--disabled) {\n    color: var(--sl-color-primary-600);\n  }\n\n  .tab.tab--closable {\n    padding-inline-end: var(--sl-spacing-small);\n  }\n\n  .tab.tab--disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .tab__close-button {\n    font-size: var(--sl-font-size-small);\n    margin-inline-start: var(--sl-spacing-small);\n  }\n\n  .tab__close-button::part(base) {\n    padding: var(--sl-spacing-3x-small);\n  }\n\n  @media (forced-colors: active) {\n    .tab.tab--active:not(.tab--disabled) {\n      outline: solid 1px transparent;\n      outline-offset: -3px;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/tab/tab.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlIconButton from '../icon-button/icon-button.js';\nimport type SlTab from './tab.js';\nimport type SlTabGroup from '../tab-group/tab-group.js';\n\ndescribe('<sl-tab>', () => {\n  it('passes accessibility test', async () => {\n    const el = await fixture<SlTab>(html`\n      <sl-tab-group>\n        <sl-tab slot=\"nav\">Test</sl-tab>\n      </sl-tab-group>\n    `);\n\n    await expect(el).to.be.accessible();\n  });\n\n  it('should render default tab', async () => {\n    const el = await fixture<SlTab>(html` <sl-tab>Test</sl-tab> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(el.getAttribute('role')).to.equal('tab');\n    expect(el.getAttribute('aria-disabled')).to.equal('false');\n    expect(el.getAttribute('aria-selected')).to.equal('false');\n    expect(el.getAttribute('tabindex')).to.equal('0');\n    expect(base.getAttribute('class')).to.equal(' tab ');\n    expect(el.active).to.equal(false);\n    expect(el.closable).to.equal(false);\n    expect(el.disabled).to.equal(false);\n  });\n\n  it('should disable tab by attribute', async () => {\n    const el = await fixture<SlTab>(html` <sl-tab disabled>Test</sl-tab> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(el.disabled).to.equal(true);\n    expect(el.getAttribute('aria-disabled')).to.equal('true');\n    expect(base.getAttribute('class')).to.equal(' tab tab--disabled ');\n    expect(el.getAttribute('tabindex')).to.equal('-1');\n  });\n\n  it('should set active tab by attribute', async () => {\n    const el = await fixture<SlTab>(html` <sl-tab active>Test</sl-tab> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(el.active).to.equal(true);\n    expect(el.getAttribute('aria-selected')).to.equal('true');\n    expect(base.getAttribute('class')).to.equal(' tab tab--active ');\n    expect(el.getAttribute('tabindex')).to.equal('0');\n  });\n\n  it('should set closable by attribute', async () => {\n    const el = await fixture<SlTab>(html` <sl-tab closable>Test</sl-tab> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const closeButton = el.shadowRoot!.querySelector('[part~=\"close-button\"]');\n\n    expect(el.closable).to.equal(true);\n    expect(base.getAttribute('class')).to.match(/tab tab--closable/);\n    expect(closeButton).not.to.be.null;\n  });\n\n  describe('focus', () => {\n    it('should focus itself', async () => {\n      const el = await fixture<SlTab>(html` <sl-tab>Test</sl-tab> `);\n\n      el.focus();\n      await el.updateComplete;\n\n      expect(document.activeElement).to.equal(el);\n    });\n  });\n\n  describe('blur', () => {\n    it('should blur itself', async () => {\n      const el = await fixture<SlTab>(html` <sl-tab>Test</sl-tab> `);\n\n      el.focus();\n      await el.updateComplete;\n\n      expect(document.activeElement).to.equal(el);\n\n      el.blur();\n      await el.updateComplete;\n\n      expect(document.activeElement).to.not.equal(el);\n    });\n  });\n\n  describe('closable', () => {\n    it('should emit close event when the close button is clicked', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" closable>General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" closable>Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n      const closeButton = tabGroup\n        .querySelectorAll('sl-tab')[0]!\n        .shadowRoot!.querySelector<SlIconButton>('[part~=\"close-button\"]')!;\n\n      const handleClose = sinon.spy();\n      const handleTabShow = sinon.spy();\n\n      tabGroup.addEventListener('sl-close', handleClose, { once: true });\n      // The sl-tab-show event shouldn't be emitted when clicking the close button\n      tabGroup.addEventListener('sl-tab-show', handleTabShow);\n\n      closeButton.click();\n      await closeButton?.updateComplete;\n\n      expect(handleClose.called).to.equal(true);\n      expect(handleTabShow.called).to.equal(false);\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/tab/tab.ts",
    "content": "import SlTab from './tab.component.js';\n\nexport * from './tab.component.js';\nexport default SlTab;\n\nSlTab.define('sl-tab');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tab': SlTab;\n  }\n}\n"
  },
  {
    "path": "src/components/tab-group/tab-group.component.ts",
    "content": "import '../../internal/scrollend-polyfill.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { eventOptions, property, query, state } from 'lit/decorators.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { scrollIntoView } from '../../internal/scroll.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport SlResizeObserver from '../resize-observer/resize-observer.component.js';\nimport styles from './tab-group.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type SlTab from '../tab/tab.js';\nimport type SlTabPanel from '../tab-panel/tab-panel.js';\n\n/**\n * @summary Tab groups organize content into a container that shows one section at a time.\n * @documentation https://shoelace.style/components/tab-group\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - Used for grouping tab panels in the tab group. Must be `<sl-tab-panel>` elements.\n * @slot nav - Used for grouping tabs in the tab group. Must be `<sl-tab>` elements.\n *\n * @event {{ name: String }} sl-tab-show - Emitted when a tab is shown.\n * @event {{ name: String }} sl-tab-hide - Emitted when a tab is hidden.\n *\n * @csspart base - The component's base wrapper.\n * @csspart nav - The tab group's navigation container where tabs are slotted in.\n * @csspart tabs - The container that wraps the tabs.\n * @csspart active-tab-indicator - The line that highlights the currently selected tab.\n * @csspart body - The tab group's body where tab panels are slotted in.\n * @csspart scroll-button - The previous/next scroll buttons that show when tabs are scrollable, an `<sl-icon-button>`.\n * @csspart scroll-button--start - The starting scroll button.\n * @csspart scroll-button--end - The ending scroll button.\n * @csspart scroll-button__base - The scroll button's exported `base` part.\n *\n * @cssproperty --indicator-color - The color of the active tab indicator.\n * @cssproperty --track-color - The color of the indicator's track (the line that separates tabs from panels).\n * @cssproperty --track-width - The width of the indicator's track (the line that separates tabs from panels).\n */\nexport default class SlTabGroup extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon-button': SlIconButton, 'sl-resize-observer': SlResizeObserver };\n\n  private activeTab?: SlTab;\n  private mutationObserver: MutationObserver;\n  private resizeObserver: ResizeObserver;\n  private tabs: SlTab[] = [];\n  private focusableTabs: SlTab[] = [];\n  private panels: SlTabPanel[] = [];\n  private readonly localize = new LocalizeController(this);\n\n  @query('.tab-group') tabGroup: HTMLElement;\n  @query('.tab-group__body') body: HTMLSlotElement;\n  @query('.tab-group__nav') nav: HTMLElement;\n  @query('.tab-group__indicator') indicator: HTMLElement;\n\n  @state() private hasScrollControls = false;\n\n  @state() private shouldHideScrollStartButton = false;\n  @state() private shouldHideScrollEndButton = false;\n\n  /** The placement of the tabs. */\n  @property() placement: 'top' | 'bottom' | 'start' | 'end' = 'top';\n\n  /**\n   * When set to auto, navigating tabs with the arrow keys will instantly show the corresponding tab panel. When set to\n   * manual, the tab will receive focus but will not show until the user presses spacebar or enter.\n   */\n  @property() activation: 'auto' | 'manual' = 'auto';\n\n  /** Disables the scroll arrows that appear when tabs overflow. */\n  @property({ attribute: 'no-scroll-controls', type: Boolean }) noScrollControls = false;\n\n  /** Prevent scroll buttons from being hidden when inactive. */\n  @property({ attribute: 'fixed-scroll-controls', type: Boolean }) fixedScrollControls = false;\n\n  connectedCallback() {\n    const whenAllDefined = Promise.all([\n      customElements.whenDefined('sl-tab'),\n      customElements.whenDefined('sl-tab-panel')\n    ]);\n\n    super.connectedCallback();\n\n    this.resizeObserver = new ResizeObserver(() => {\n      this.repositionIndicator();\n      this.updateScrollControls();\n    });\n\n    this.mutationObserver = new MutationObserver(mutations => {\n      // Make sure to only observe the direct children of the tab group\n      // instead of other sub elements that might be slotted in.\n      // @see https://github.com/shoelace-style/shoelace/issues/2320\n      const instanceMutations = mutations.filter(({ target }) => {\n        if (target === this) return true; // Allow self updates\n        if ((target as HTMLElement).closest('sl-tab-group') !== this) return false; // We are not direct children\n\n        // We should only care about changes to the tab or tab panel\n        const tagName = (target as HTMLElement).tagName.toLowerCase();\n        return tagName === 'sl-tab' || tagName === 'sl-tab-panel';\n      });\n\n      if (instanceMutations.length === 0) {\n        return;\n      }\n\n      // Update aria labels when the DOM changes\n      if (instanceMutations.some(m => !['aria-labelledby', 'aria-controls'].includes(m.attributeName!))) {\n        setTimeout(() => this.setAriaLabels());\n      }\n\n      // Sync tabs when disabled states change\n      if (instanceMutations.some(m => m.attributeName === 'disabled')) {\n        this.syncTabsAndPanels();\n        // sync tabs when active state on tab changes\n      } else if (instanceMutations.some(m => m.attributeName === 'active')) {\n        const tabs = instanceMutations\n          .filter(m => m.attributeName === 'active' && (m.target as HTMLElement).tagName.toLowerCase() === 'sl-tab')\n          .map(m => m.target as SlTab);\n        const newActiveTab = tabs.find(tab => tab.active);\n\n        if (newActiveTab) {\n          this.setActiveTab(newActiveTab);\n        }\n      }\n    });\n\n    // After the first update...\n    this.updateComplete.then(() => {\n      this.syncTabsAndPanels();\n\n      this.mutationObserver.observe(this, {\n        attributes: true,\n        attributeFilter: ['active', 'disabled', 'name', 'panel'],\n        childList: true,\n        subtree: true\n      });\n\n      this.resizeObserver.observe(this.nav);\n\n      // Wait for tabs and tab panels to be registered\n      whenAllDefined.then(() => {\n        // Set initial tab state when the tabs become visible\n        const intersectionObserver = new IntersectionObserver((entries, observer) => {\n          if (entries[0].intersectionRatio > 0) {\n            this.setAriaLabels();\n            this.setActiveTab(this.getActiveTab() ?? this.tabs[0], { emitEvents: false });\n            observer.unobserve(entries[0].target);\n          }\n        });\n        intersectionObserver.observe(this.tabGroup);\n      });\n    });\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.mutationObserver?.disconnect();\n\n    if (this.nav) {\n      this.resizeObserver?.unobserve(this.nav);\n    }\n  }\n\n  private getAllTabs() {\n    const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=\"nav\"]')!;\n\n    return slot.assignedElements() as SlTab[];\n  }\n\n  private getAllPanels() {\n    return [...this.body.assignedElements()].filter(el => el.tagName.toLowerCase() === 'sl-tab-panel') as [SlTabPanel];\n  }\n\n  private getActiveTab() {\n    return this.tabs.find(el => el.active);\n  }\n\n  private handleClick(event: MouseEvent) {\n    const target = event.target as HTMLElement;\n    const tab = target.closest('sl-tab');\n    const tabGroup = tab?.closest('sl-tab-group');\n\n    // Ensure the target tab is in this tab group\n    if (tabGroup !== this) {\n      return;\n    }\n\n    if (tab !== null) {\n      this.setActiveTab(tab, { scrollBehavior: 'smooth' });\n    }\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    const target = event.target as HTMLElement;\n    const tab = target.closest('sl-tab');\n    const tabGroup = tab?.closest('sl-tab-group');\n\n    // Ensure the target tab is in this tab group\n    if (tabGroup !== this) {\n      return;\n    }\n\n    // Activate a tab\n    if (['Enter', ' '].includes(event.key)) {\n      if (tab !== null) {\n        this.setActiveTab(tab, { scrollBehavior: 'smooth' });\n        event.preventDefault();\n      }\n    }\n\n    // Move focus left or right\n    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {\n      const activeEl = this.tabs.find(t => t.matches(':focus'));\n      const isRtl = this.localize.dir() === 'rtl';\n      let nextTab: null | SlTab = null;\n\n      if (activeEl?.tagName.toLowerCase() === 'sl-tab') {\n        if (event.key === 'Home') {\n          nextTab = this.focusableTabs[0];\n        } else if (event.key === 'End') {\n          nextTab = this.focusableTabs[this.focusableTabs.length - 1];\n        } else if (\n          (['top', 'bottom'].includes(this.placement) && event.key === (isRtl ? 'ArrowRight' : 'ArrowLeft')) ||\n          (['start', 'end'].includes(this.placement) && event.key === 'ArrowUp')\n        ) {\n          const currentIndex = this.tabs.findIndex(el => el === activeEl);\n          nextTab = this.findNextFocusableTab(currentIndex, 'backward');\n        } else if (\n          (['top', 'bottom'].includes(this.placement) && event.key === (isRtl ? 'ArrowLeft' : 'ArrowRight')) ||\n          (['start', 'end'].includes(this.placement) && event.key === 'ArrowDown')\n        ) {\n          const currentIndex = this.tabs.findIndex(el => el === activeEl);\n          nextTab = this.findNextFocusableTab(currentIndex, 'forward');\n        }\n\n        if (!nextTab) {\n          return;\n        }\n\n        nextTab.tabIndex = 0;\n        nextTab.focus({ preventScroll: true });\n\n        if (this.activation === 'auto') {\n          this.setActiveTab(nextTab, { scrollBehavior: 'smooth' });\n        } else {\n          this.tabs.forEach(tabEl => {\n            tabEl.tabIndex = tabEl === nextTab ? 0 : -1;\n          });\n        }\n\n        if (['top', 'bottom'].includes(this.placement)) {\n          scrollIntoView(nextTab, this.nav, 'horizontal');\n        }\n\n        event.preventDefault();\n      }\n    }\n  }\n\n  private handleScrollToStart() {\n    this.nav.scroll({\n      left:\n        this.localize.dir() === 'rtl'\n          ? this.nav.scrollLeft + this.nav.clientWidth\n          : this.nav.scrollLeft - this.nav.clientWidth,\n      behavior: 'smooth'\n    });\n  }\n\n  private handleScrollToEnd() {\n    this.nav.scroll({\n      left:\n        this.localize.dir() === 'rtl'\n          ? this.nav.scrollLeft - this.nav.clientWidth\n          : this.nav.scrollLeft + this.nav.clientWidth,\n      behavior: 'smooth'\n    });\n  }\n\n  private setActiveTab(tab: SlTab, options?: { emitEvents?: boolean; scrollBehavior?: 'auto' | 'smooth' }) {\n    options = {\n      emitEvents: true,\n      scrollBehavior: 'auto',\n      ...options\n    };\n\n    if (tab !== this.activeTab && !tab.disabled) {\n      const previousTab = this.activeTab;\n      this.activeTab = tab;\n\n      // Sync active tab and panel\n      this.tabs.forEach(el => {\n        el.active = el === this.activeTab;\n        el.tabIndex = el === this.activeTab ? 0 : -1;\n      });\n      this.panels.forEach(el => (el.active = el.name === this.activeTab?.panel));\n      this.syncIndicator();\n\n      if (['top', 'bottom'].includes(this.placement)) {\n        scrollIntoView(this.activeTab, this.nav, 'horizontal', options.scrollBehavior);\n      }\n\n      // Emit events\n      if (options.emitEvents) {\n        if (previousTab) {\n          this.emit('sl-tab-hide', { detail: { name: previousTab.panel } });\n        }\n\n        this.emit('sl-tab-show', { detail: { name: this.activeTab.panel } });\n      }\n    }\n  }\n\n  private setAriaLabels() {\n    // Link each tab with its corresponding panel\n    this.tabs.forEach(tab => {\n      const panel = this.panels.find(el => el.name === tab.panel);\n      if (panel) {\n        tab.setAttribute('aria-controls', panel.getAttribute('id')!);\n        panel.setAttribute('aria-labelledby', tab.getAttribute('id')!);\n      }\n    });\n  }\n\n  private repositionIndicator() {\n    const currentTab = this.getActiveTab();\n\n    if (!currentTab) {\n      return;\n    }\n\n    const width = currentTab.clientWidth;\n    const height = currentTab.clientHeight;\n    const isRtl = this.localize.dir() === 'rtl';\n\n    // We can't used offsetLeft/offsetTop here due to a shadow parent issue where neither can getBoundingClientRect\n    // because it provides invalid values for animating elements: https://bugs.chromium.org/p/chromium/issues/detail?id=920069\n    const allTabs = this.getAllTabs();\n    const precedingTabs = allTabs.slice(0, allTabs.indexOf(currentTab));\n    const offset = precedingTabs.reduce(\n      (previous, current) => ({\n        left: previous.left + current.clientWidth,\n        top: previous.top + current.clientHeight\n      }),\n      { left: 0, top: 0 }\n    );\n\n    switch (this.placement) {\n      case 'top':\n      case 'bottom':\n        this.indicator.style.width = `${width}px`;\n        this.indicator.style.height = 'auto';\n        this.indicator.style.translate = isRtl ? `${-1 * offset.left}px` : `${offset.left}px`;\n        break;\n\n      case 'start':\n      case 'end':\n        this.indicator.style.width = 'auto';\n        this.indicator.style.height = `${height}px`;\n        this.indicator.style.translate = `0 ${offset.top}px`;\n        break;\n    }\n  }\n\n  // This stores tabs and panels so we can refer to a cache instead of calling querySelectorAll() multiple times.\n  private syncTabsAndPanels() {\n    this.tabs = this.getAllTabs();\n    this.focusableTabs = this.tabs.filter(el => !el.disabled);\n\n    this.panels = this.getAllPanels();\n    this.syncIndicator();\n\n    // After updating, show or hide scroll controls as needed\n    this.updateComplete.then(() => this.updateScrollControls());\n  }\n\n  private findNextFocusableTab(currentIndex: number, direction: 'forward' | 'backward') {\n    let nextTab = null;\n    const iterator = direction === 'forward' ? 1 : -1;\n    let nextIndex = currentIndex + iterator;\n\n    while (currentIndex < this.tabs.length) {\n      nextTab = this.tabs[nextIndex] || null;\n\n      if (nextTab === null) {\n        // This is where wrapping happens. If we're moving forward and get to the end, then we jump to the beginning. If we're moving backward and get to the start, then we jump to the end.\n        if (direction === 'forward') {\n          nextTab = this.focusableTabs[0];\n        } else {\n          nextTab = this.focusableTabs[this.focusableTabs.length - 1];\n        }\n        break;\n      }\n\n      if (!nextTab.disabled) {\n        break;\n      }\n\n      nextIndex += iterator;\n    }\n\n    return nextTab;\n  }\n\n  /**\n   * The reality of the browser means that we can't expect the scroll position to be exactly what we want it to be, so\n   * we add one pixel of wiggle room to our calculations.\n   */\n  private scrollOffset = 1;\n\n  @eventOptions({ passive: true })\n  private updateScrollButtons() {\n    if (this.hasScrollControls && !this.fixedScrollControls) {\n      this.shouldHideScrollStartButton = this.scrollFromStart() <= this.scrollOffset;\n      this.shouldHideScrollEndButton = this.isScrolledToEnd();\n    }\n  }\n\n  private isScrolledToEnd() {\n    return this.scrollFromStart() + this.nav.clientWidth >= this.nav.scrollWidth - this.scrollOffset;\n  }\n\n  private scrollFromStart() {\n    return this.localize.dir() === 'rtl' ? -this.nav.scrollLeft : this.nav.scrollLeft;\n  }\n\n  @watch('noScrollControls', { waitUntilFirstUpdate: true })\n  updateScrollControls() {\n    if (this.noScrollControls) {\n      this.hasScrollControls = false;\n    } else {\n      // In most cases, we can compare scrollWidth to clientWidth to determine if scroll controls should show. However,\n      // Safari appears to calculate this incorrectly when zoomed at 110%, causing the controls to toggle indefinitely.\n      // Adding a single pixel to the comparison seems to resolve it.\n      //\n      // See https://github.com/shoelace-style/shoelace/issues/1839\n      this.hasScrollControls =\n        ['top', 'bottom'].includes(this.placement) && this.nav.scrollWidth > this.nav.clientWidth + 1;\n    }\n\n    this.updateScrollButtons();\n  }\n\n  @watch('placement', { waitUntilFirstUpdate: true })\n  syncIndicator() {\n    const tab = this.getActiveTab();\n\n    if (tab) {\n      this.indicator.style.display = 'block';\n      this.repositionIndicator();\n    } else {\n      this.indicator.style.display = 'none';\n    }\n  }\n\n  /** Shows the specified tab panel. */\n  show(panel: string) {\n    const tab = this.tabs.find(el => el.panel === panel);\n\n    if (tab) {\n      this.setActiveTab(tab, { scrollBehavior: 'smooth' });\n    }\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n\n    return html`\n      <div\n        part=\"base\"\n        class=${classMap({\n          'tab-group': true,\n          'tab-group--top': this.placement === 'top',\n          'tab-group--bottom': this.placement === 'bottom',\n          'tab-group--start': this.placement === 'start',\n          'tab-group--end': this.placement === 'end',\n          'tab-group--rtl': this.localize.dir() === 'rtl',\n          'tab-group--has-scroll-controls': this.hasScrollControls\n        })}\n        @click=${this.handleClick}\n        @keydown=${this.handleKeyDown}\n      >\n        <div class=\"tab-group__nav-container\" part=\"nav\">\n          ${this.hasScrollControls\n            ? html`\n                <sl-icon-button\n                  part=\"scroll-button scroll-button--start\"\n                  exportparts=\"base:scroll-button__base\"\n                  class=${classMap({\n                    'tab-group__scroll-button': true,\n                    'tab-group__scroll-button--start': true,\n                    'tab-group__scroll-button--start--hidden': this.shouldHideScrollStartButton\n                  })}\n                  name=${isRtl ? 'chevron-right' : 'chevron-left'}\n                  library=\"system\"\n                  tabindex=\"-1\"\n                  aria-hidden=\"true\"\n                  label=${this.localize.term('scrollToStart')}\n                  @click=${this.handleScrollToStart}\n                ></sl-icon-button>\n              `\n            : ''}\n\n          <div class=\"tab-group__nav\" @scrollend=${this.updateScrollButtons}>\n            <div part=\"tabs\" class=\"tab-group__tabs\" role=\"tablist\">\n              <div part=\"active-tab-indicator\" class=\"tab-group__indicator\"></div>\n              <sl-resize-observer @sl-resize=${this.syncIndicator}>\n                <slot name=\"nav\" @slotchange=${this.syncTabsAndPanels}></slot>\n              </sl-resize-observer>\n            </div>\n          </div>\n\n          ${this.hasScrollControls\n            ? html`\n                <sl-icon-button\n                  part=\"scroll-button scroll-button--end\"\n                  exportparts=\"base:scroll-button__base\"\n                  class=${classMap({\n                    'tab-group__scroll-button': true,\n                    'tab-group__scroll-button--end': true,\n                    'tab-group__scroll-button--end--hidden': this.shouldHideScrollEndButton\n                  })}\n                  name=${isRtl ? 'chevron-left' : 'chevron-right'}\n                  library=\"system\"\n                  tabindex=\"-1\"\n                  aria-hidden=\"true\"\n                  label=${this.localize.term('scrollToEnd')}\n                  @click=${this.handleScrollToEnd}\n                ></sl-icon-button>\n              `\n            : ''}\n        </div>\n\n        <slot part=\"body\" class=\"tab-group__body\" @slotchange=${this.syncTabsAndPanels}></slot>\n      </div>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tab-group': SlTabGroup;\n  }\n}\n"
  },
  {
    "path": "src/components/tab-group/tab-group.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --indicator-color: var(--sl-color-primary-600);\n    --track-color: var(--sl-color-neutral-200);\n    --track-width: 2px;\n\n    display: block;\n  }\n\n  .tab-group {\n    display: flex;\n    border-radius: 0;\n  }\n\n  .tab-group__tabs {\n    display: flex;\n    position: relative;\n  }\n\n  .tab-group__indicator {\n    position: absolute;\n    transition:\n      var(--sl-transition-fast) translate ease,\n      var(--sl-transition-fast) width ease;\n  }\n\n  .tab-group--has-scroll-controls .tab-group__nav-container {\n    position: relative;\n    padding: 0 var(--sl-spacing-x-large);\n  }\n\n  .tab-group--has-scroll-controls .tab-group__scroll-button--start--hidden,\n  .tab-group--has-scroll-controls .tab-group__scroll-button--end--hidden {\n    visibility: hidden;\n  }\n\n  .tab-group__body {\n    display: block;\n    overflow: auto;\n  }\n\n  .tab-group__scroll-button {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    width: var(--sl-spacing-x-large);\n  }\n\n  .tab-group__scroll-button--start {\n    left: 0;\n  }\n\n  .tab-group__scroll-button--end {\n    right: 0;\n  }\n\n  .tab-group--rtl .tab-group__scroll-button--start {\n    left: auto;\n    right: 0;\n  }\n\n  .tab-group--rtl .tab-group__scroll-button--end {\n    left: 0;\n    right: auto;\n  }\n\n  /*\n   * Top\n   */\n\n  .tab-group--top {\n    flex-direction: column;\n  }\n\n  .tab-group--top .tab-group__nav-container {\n    order: 1;\n  }\n\n  .tab-group--top .tab-group__nav {\n    display: flex;\n    overflow-x: auto;\n\n    /* Hide scrollbar in Firefox */\n    scrollbar-width: none;\n  }\n\n  /* Hide scrollbar in Chrome/Safari */\n  .tab-group--top .tab-group__nav::-webkit-scrollbar {\n    width: 0;\n    height: 0;\n  }\n\n  .tab-group--top .tab-group__tabs {\n    flex: 1 1 auto;\n    position: relative;\n    flex-direction: row;\n    border-bottom: solid var(--track-width) var(--track-color);\n  }\n\n  .tab-group--top .tab-group__indicator {\n    bottom: calc(-1 * var(--track-width));\n    border-bottom: solid var(--track-width) var(--indicator-color);\n  }\n\n  .tab-group--top .tab-group__body {\n    order: 2;\n  }\n\n  .tab-group--top ::slotted(sl-tab-panel) {\n    --padding: var(--sl-spacing-medium) 0;\n  }\n\n  /*\n   * Bottom\n   */\n\n  .tab-group--bottom {\n    flex-direction: column;\n  }\n\n  .tab-group--bottom .tab-group__nav-container {\n    order: 2;\n  }\n\n  .tab-group--bottom .tab-group__nav {\n    display: flex;\n    overflow-x: auto;\n\n    /* Hide scrollbar in Firefox */\n    scrollbar-width: none;\n  }\n\n  /* Hide scrollbar in Chrome/Safari */\n  .tab-group--bottom .tab-group__nav::-webkit-scrollbar {\n    width: 0;\n    height: 0;\n  }\n\n  .tab-group--bottom .tab-group__tabs {\n    flex: 1 1 auto;\n    position: relative;\n    flex-direction: row;\n    border-top: solid var(--track-width) var(--track-color);\n  }\n\n  .tab-group--bottom .tab-group__indicator {\n    top: calc(-1 * var(--track-width));\n    border-top: solid var(--track-width) var(--indicator-color);\n  }\n\n  .tab-group--bottom .tab-group__body {\n    order: 1;\n  }\n\n  .tab-group--bottom ::slotted(sl-tab-panel) {\n    --padding: var(--sl-spacing-medium) 0;\n  }\n\n  /*\n   * Start\n   */\n\n  .tab-group--start {\n    flex-direction: row;\n  }\n\n  .tab-group--start .tab-group__nav-container {\n    order: 1;\n  }\n\n  .tab-group--start .tab-group__tabs {\n    flex: 0 0 auto;\n    flex-direction: column;\n    border-inline-end: solid var(--track-width) var(--track-color);\n  }\n\n  .tab-group--start .tab-group__indicator {\n    right: calc(-1 * var(--track-width));\n    border-right: solid var(--track-width) var(--indicator-color);\n  }\n\n  .tab-group--start.tab-group--rtl .tab-group__indicator {\n    right: auto;\n    left: calc(-1 * var(--track-width));\n  }\n\n  .tab-group--start .tab-group__body {\n    flex: 1 1 auto;\n    order: 2;\n  }\n\n  .tab-group--start ::slotted(sl-tab-panel) {\n    --padding: 0 var(--sl-spacing-medium);\n  }\n\n  /*\n   * End\n   */\n\n  .tab-group--end {\n    flex-direction: row;\n  }\n\n  .tab-group--end .tab-group__nav-container {\n    order: 2;\n  }\n\n  .tab-group--end .tab-group__tabs {\n    flex: 0 0 auto;\n    flex-direction: column;\n    border-left: solid var(--track-width) var(--track-color);\n  }\n\n  .tab-group--end .tab-group__indicator {\n    left: calc(-1 * var(--track-width));\n    border-inline-start: solid var(--track-width) var(--indicator-color);\n  }\n\n  .tab-group--end.tab-group--rtl .tab-group__indicator {\n    right: calc(-1 * var(--track-width));\n    left: auto;\n  }\n\n  .tab-group--end .tab-group__body {\n    flex: 1 1 auto;\n    order: 1;\n  }\n\n  .tab-group--end ::slotted(sl-tab-panel) {\n    --padding: 0 var(--sl-spacing-medium);\n  }\n`;\n"
  },
  {
    "path": "src/components/tab-group/tab-group.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, elementUpdated, expect, fixture, oneEvent, waitUntil } from '@open-wc/testing';\nimport { clickOnElement } from '../../internal/test.js';\nimport { html } from 'lit';\nimport { isElementVisibleFromOverflow } from '../../internal/test/element-visible-overflow.js';\nimport { queryByTestId } from '../../internal/test/data-testid-helpers.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport { waitForScrollingToEnd } from '../../internal/test/wait-for-scrolling.js';\nimport type { HTMLTemplateResult } from 'lit';\nimport type { SlTabShowEvent } from '../../events/sl-tab-show.js';\nimport type SlTab from '../tab/tab.js';\nimport type SlTabGroup from './tab-group.js';\nimport type SlTabPanel from '../tab-panel/tab-panel.js';\n\ninterface ClientRectangles {\n  body?: DOMRect;\n  navigation?: DOMRect;\n}\n\nconst waitForScrollButtonsToBeRendered = async (tabGroup: SlTabGroup): Promise<void> => {\n  await waitUntil(() => {\n    const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n    return scrollButtons?.length === 2;\n  });\n};\n\nconst getClientRectangles = (tabGroup: SlTabGroup): ClientRectangles => {\n  const shadowRoot = tabGroup.shadowRoot;\n  if (shadowRoot) {\n    const nav = shadowRoot.querySelector<HTMLElement>('[part=nav]');\n    const body = shadowRoot.querySelector<HTMLElement>('[part=body]');\n    return {\n      body: body?.getBoundingClientRect(),\n      navigation: nav?.getBoundingClientRect()\n    };\n  }\n  return {};\n};\n\nconst expectHeaderToBeVisible = (container: HTMLElement, dataTestId: string): void => {\n  const generalHeader = queryByTestId<SlTab>(container, dataTestId);\n  expect(generalHeader).not.to.be.null;\n  expect(generalHeader).to.be.visible;\n};\n\nconst expectOnlyOneTabPanelToBeActive = async (container: HTMLElement, dataTestIdOfActiveTab: string) => {\n  await waitUntil(() => {\n    const tabPanels = Array.from(container.getElementsByTagName('sl-tab-panel'));\n    const activeTabPanels = tabPanels.filter((element: SlTabPanel) => element.hasAttribute('active'));\n    return activeTabPanels.length === 1;\n  });\n  const tabPanels = Array.from(container.getElementsByTagName('sl-tab-panel'));\n  const activeTabPanels = tabPanels.filter((element: SlTabPanel) => element.hasAttribute('active'));\n  expect(activeTabPanels).to.have.lengthOf(1);\n  expect(activeTabPanels[0]).to.have.attribute('data-testid', dataTestIdOfActiveTab);\n};\n\nconst expectPromiseToHaveName = async (showEventPromise: Promise<SlTabShowEvent>, expectedName: string) => {\n  const showEvent = await showEventPromise;\n  expect(showEvent.detail.name).to.equal(expectedName);\n};\n\nconst waitForHeaderToBeActive = async (container: HTMLElement, headerTestId: string): Promise<SlTab> => {\n  const generalHeader = queryByTestId<SlTab>(container, headerTestId);\n  await waitUntil(() => {\n    return generalHeader?.hasAttribute('active');\n  });\n  if (generalHeader) {\n    return generalHeader;\n  } else {\n    throw new Error(`did not find error with testid=${headerTestId}`);\n  }\n};\n\ndescribe('<sl-tab-group>', () => {\n  it('renders', async () => {\n    const tabGroup = await fixture<SlTabGroup>(html`\n      <sl-tab-group>\n        <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n        <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n      </sl-tab-group>\n    `);\n\n    expect(tabGroup).to.be.visible;\n  });\n\n  it('should not throw error when unmounted too fast', async () => {\n    const el = await fixture(html` <div></div> `);\n\n    el.innerHTML = '<sl-tab-group></sl-tab-group>';\n    el.innerHTML = '';\n  });\n\n  it('is accessible', async () => {\n    const tabGroup = await fixture<SlTabGroup>(html`\n      <sl-tab-group>\n        <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n        <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n      </sl-tab-group>\n    `);\n\n    await expect(tabGroup).to.be.accessible();\n  });\n\n  it('displays all tabs', async () => {\n    const tabGroup = await fixture<SlTabGroup>(html`\n      <sl-tab-group>\n        <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-tab-header\">General</sl-tab>\n        <sl-tab slot=\"nav\" panel=\"disabled\" disabled data-testid=\"disabled-tab-header\">Disabled</sl-tab>\n        <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n        <sl-tab-panel name=\"disabled\">This is a disabled tab panel.</sl-tab-panel>\n      </sl-tab-group>\n    `);\n\n    expectHeaderToBeVisible(tabGroup, 'general-tab-header');\n    expectHeaderToBeVisible(tabGroup, 'disabled-tab-header');\n  });\n\n  it('shows the first tab to be active by default', async () => {\n    const tabGroup = await fixture<SlTabGroup>(html`\n      <sl-tab-group>\n        <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n        <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n        <sl-tab-panel name=\"general\" data-testid=\"general-tab-content\">This is the general tab panel.</sl-tab-panel>\n        <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n      </sl-tab-group>\n    `);\n\n    await expectOnlyOneTabPanelToBeActive(tabGroup, 'general-tab-content');\n  });\n\n  describe('proper positioning', () => {\n    it('shows the header above the tabs by default', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      await aTimeout(0);\n\n      const clientRectangles = getClientRectangles(tabGroup);\n      expect(clientRectangles.body?.top).to.be.greaterThanOrEqual(clientRectangles.navigation?.bottom || -Infinity);\n    });\n\n    it('shows the header below the tabs by setting placement to bottom', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n      tabGroup.placement = 'bottom';\n\n      await aTimeout(0);\n\n      const clientRectangles = getClientRectangles(tabGroup);\n      expect(clientRectangles.body?.bottom).to.be.lessThanOrEqual(clientRectangles.navigation?.top || +Infinity);\n    });\n\n    it('shows the header left of the tabs by setting placement to start', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n      tabGroup.placement = 'start';\n\n      await aTimeout(0);\n\n      const clientRectangles = getClientRectangles(tabGroup);\n      expect(clientRectangles.body?.left).to.be.greaterThanOrEqual(clientRectangles.navigation?.right || -Infinity);\n    });\n\n    it('shows the header right of the tabs by setting placement to end', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\">General</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n      tabGroup.placement = 'end';\n\n      await aTimeout(0);\n\n      const clientRectangles = getClientRectangles(tabGroup);\n      expect(clientRectangles.body?.right).to.be.lessThanOrEqual(clientRectangles.navigation?.left || -Infinity);\n    });\n  });\n\n  describe('scrolling behavior', () => {\n    const generateTabs = (n: number): HTMLTemplateResult[] => {\n      const result: HTMLTemplateResult[] = [];\n      for (let i = 0; i < n; i++) {\n        result.push(\n          html`<sl-tab slot=\"nav\" panel=\"tab-${i}\">Tab ${i}</sl-tab>\n            <sl-tab-panel name=\"tab-${i}\">Content of tab ${i}0</sl-tab-panel> `\n        );\n      }\n      return result;\n    };\n\n    before(() => {\n      // disabling failing on resize observer ... unfortunately on webkit this is not really specific\n      // https://github.com/WICG/resize-observer/issues/38#issuecomment-422126006\n      // https://stackoverflow.com/a/64197640\n      const errorHandler = window.onerror;\n      window.onerror = (\n        event: string | Event,\n        source?: string | undefined,\n        lineno?: number | undefined,\n        colno?: number | undefined,\n        error?: Error | undefined\n      ) => {\n        if ((event as string).includes('ResizeObserver') || event === 'Script error.') {\n          return true;\n        } else if (errorHandler) {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n          return errorHandler(event, source, lineno, colno, error);\n        } else {\n          return true;\n        }\n      };\n    });\n\n    it('shows scroll buttons on too many tabs', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`<sl-tab-group> ${generateTabs(30)} </sl-tab-group>`);\n\n      await waitForScrollButtonsToBeRendered(tabGroup);\n\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons, 'Both scroll buttons should be shown').to.have.length(2);\n\n      tabGroup.disconnectedCallback();\n    });\n\n    it('does not show scroll buttons on too many tabs if deactivated', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`<sl-tab-group> ${generateTabs(30)} </sl-tab-group>`);\n      tabGroup.noScrollControls = true;\n\n      await aTimeout(0);\n\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons).to.have.length(0);\n    });\n\n    it('does not show scroll buttons if all tabs fit on the screen', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`<sl-tab-group> ${generateTabs(2)} </sl-tab-group>`);\n\n      await aTimeout(0);\n\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons).to.have.length(0);\n    });\n\n    // TODO - this fails sporadically, likely due to a timing issue. It tests fine manually.\n    it.skip('does not show scroll buttons if placement is start', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`<sl-tab-group> ${generateTabs(50)} </sl-tab-group>`);\n      tabGroup.placement = 'start';\n\n      await aTimeout(0);\n\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons).to.have.length(0);\n    });\n\n    // TODO - this fails sporadically, likely due to a timing issue. It tests fine manually.\n    it.skip('does not show scroll buttons if placement is end', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`<sl-tab-group> ${generateTabs(50)} </sl-tab-group>`);\n      tabGroup.placement = 'end';\n\n      await aTimeout(0);\n\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons).to.have.length(0);\n    });\n\n    // TODO - this fails sporadically, likely due to a timing issue. It tests fine manually.\n    it.skip('does scroll on scroll button click', async () => {\n      const numberOfElements = 15;\n      const tabGroup = await fixture<SlTabGroup>(\n        html`<sl-tab-group> ${generateTabs(numberOfElements)} </sl-tab-group>`\n      );\n\n      await waitForScrollButtonsToBeRendered(tabGroup);\n      const scrollButtons = tabGroup.shadowRoot?.querySelectorAll('sl-icon-button');\n      expect(scrollButtons).to.have.length(2);\n\n      const firstTab = tabGroup.querySelector('[panel=\"tab-0\"]');\n      expect(firstTab).not.to.be.null;\n      const lastTab = tabGroup.querySelector(`[panel=\"tab-${numberOfElements - 1}\"]`);\n      expect(lastTab).not.to.be.null;\n      expect(isElementVisibleFromOverflow(tabGroup, firstTab!)).to.be.true;\n      expect(isElementVisibleFromOverflow(tabGroup, lastTab!)).to.be.false;\n\n      const scrollToRightButton = tabGroup.shadowRoot?.querySelector('sl-icon-button[part*=\"scroll-button--end\"]');\n      expect(scrollToRightButton).not.to.be.null;\n      await clickOnElement(scrollToRightButton!);\n\n      await elementUpdated(tabGroup);\n      await waitForScrollingToEnd(firstTab!);\n      await waitForScrollingToEnd(lastTab!);\n\n      expect(isElementVisibleFromOverflow(tabGroup, firstTab!)).to.be.false;\n      expect(isElementVisibleFromOverflow(tabGroup, lastTab!)).to.be.true;\n    });\n  });\n\n  describe('tab selection', () => {\n    const expectCustomTabToBeActiveAfter = async (tabGroup: SlTabGroup, action: () => Promise<void>): Promise<void> => {\n      const generalHeader = await waitForHeaderToBeActive(tabGroup, 'general-header');\n      generalHeader.focus();\n\n      const customHeader = queryByTestId<SlTab>(tabGroup, 'custom-header');\n      expect(customHeader).not.to.have.attribute('active');\n\n      const showEventPromise = oneEvent(tabGroup, 'sl-tab-show') as Promise<SlTabShowEvent>;\n      await action();\n\n      expect(customHeader).to.have.attribute('active');\n      await expectPromiseToHaveName(showEventPromise, 'custom');\n      return expectOnlyOneTabPanelToBeActive(tabGroup, 'custom-tab-content');\n    };\n\n    const expectGeneralTabToBeStillActiveAfter = async (\n      tabGroup: SlTabGroup,\n      action: () => Promise<void>\n    ): Promise<void> => {\n      const generalHeader = await waitForHeaderToBeActive(tabGroup, 'general-header');\n      generalHeader.focus();\n\n      let showEventFired = false;\n      let hideEventFired = false;\n      oneEvent(tabGroup, 'sl-tab-show').then(() => (showEventFired = true));\n      oneEvent(tabGroup, 'sl-tab-hide').then(() => (hideEventFired = true));\n      await action();\n\n      expect(generalHeader).to.have.attribute('active');\n      expect(showEventFired).to.be.false;\n      expect(hideEventFired).to.be.false;\n      return expectOnlyOneTabPanelToBeActive(tabGroup, 'general-tab-content');\n    };\n\n    it('selects a tab by clicking on it', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" data-testid=\"custom-header\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\" data-testid=\"custom-tab-content\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      const customHeader = queryByTestId<SlTab>(tabGroup, 'custom-header');\n      return expectCustomTabToBeActiveAfter(tabGroup, () => clickOnElement(customHeader!));\n    });\n\n    it('selects a tab by changing it via active property', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" data-testid=\"custom-header\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\" data-testid=\"custom-tab-content\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      const customHeader = queryByTestId<SlTab>(tabGroup, 'custom-header')!;\n      const generalHeader = await waitForHeaderToBeActive(tabGroup, 'general-header');\n      generalHeader.focus();\n\n      expect(customHeader).not.to.have.attribute('active');\n\n      const showEventPromise = oneEvent(tabGroup, 'sl-tab-show') as Promise<SlTabShowEvent>;\n      customHeader.active = true;\n\n      await tabGroup.updateComplete;\n      expect(customHeader).to.have.attribute('active');\n      await expectPromiseToHaveName(showEventPromise, 'custom');\n      return expectOnlyOneTabPanelToBeActive(tabGroup, 'custom-tab-content');\n    });\n\n    it('does not change if the active tab is reselected', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\" data-testid=\"general-tab-content\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      const generalHeader = queryByTestId(tabGroup, 'general-header');\n      return expectGeneralTabToBeStillActiveAfter(tabGroup, () => clickOnElement(generalHeader!));\n    });\n\n    it('does not change if a disabled tab is clicked', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"disabled\" data-testid=\"disabled-header\" disabled>disabled</sl-tab>\n          <sl-tab-panel name=\"general\" data-testid=\"general-tab-content\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"disabled\">This is the disabled tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      const disabledHeader = queryByTestId(tabGroup, 'disabled-header');\n      return expectGeneralTabToBeStillActiveAfter(tabGroup, () => clickOnElement(disabledHeader!));\n    });\n\n    it('selects a tab by using the arrow keys', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" data-testid=\"custom-header\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\" data-testid=\"custom-tab-content\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      return expectCustomTabToBeActiveAfter(tabGroup, () => sendKeys({ press: 'ArrowRight' }));\n    });\n\n    it('selects a tab by using the arrow keys and enter if activation is set to manual', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" data-testid=\"custom-header\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\" data-testid=\"custom-tab-content\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n      tabGroup.activation = 'manual';\n\n      const generalHeader = await waitForHeaderToBeActive(tabGroup, 'general-header');\n      generalHeader.focus();\n\n      const customHeader = queryByTestId<SlTab>(tabGroup, 'custom-header');\n      expect(customHeader).not.to.have.attribute('active');\n\n      const showEventPromise = oneEvent(tabGroup, 'sl-tab-show') as Promise<SlTabShowEvent>;\n      await sendKeys({ press: 'ArrowRight' });\n      await aTimeout(0);\n      expect(generalHeader).to.have.attribute('active');\n\n      await sendKeys({ press: 'Enter' });\n\n      expect(customHeader).to.have.attribute('active');\n      await expectPromiseToHaveName(showEventPromise, 'custom');\n      return expectOnlyOneTabPanelToBeActive(tabGroup, 'custom-tab-content');\n    });\n\n    it('does not allow selection of disabled tabs with arrow keys', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"disabled\" disabled>Disabled</sl-tab>\n          <sl-tab-panel name=\"general\" data-testid=\"general-tab-content\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"disabled\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      return expectGeneralTabToBeStillActiveAfter(tabGroup, () => sendKeys({ press: 'ArrowRight' }));\n    });\n\n    it('selects a tab by using the show function', async () => {\n      const tabGroup = await fixture<SlTabGroup>(html`\n        <sl-tab-group>\n          <sl-tab slot=\"nav\" panel=\"general\" data-testid=\"general-header\">General</sl-tab>\n          <sl-tab slot=\"nav\" panel=\"custom\" data-testid=\"custom-header\">Custom</sl-tab>\n          <sl-tab-panel name=\"general\">This is the general tab panel.</sl-tab-panel>\n          <sl-tab-panel name=\"custom\" data-testid=\"custom-tab-content\">This is the custom tab panel.</sl-tab-panel>\n        </sl-tab-group>\n      `);\n\n      return expectCustomTabToBeActiveAfter(tabGroup, () => {\n        tabGroup.show('custom');\n        return aTimeout(0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/tab-group/tab-group.ts",
    "content": "import SlTabGroup from './tab-group.component.js';\n\nexport * from './tab-group.component.js';\nexport default SlTabGroup;\n\nSlTabGroup.define('sl-tab-group');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tab-group': SlTabGroup;\n  }\n}\n"
  },
  {
    "path": "src/components/tab-panel/tab-panel.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './tab-panel.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\nlet id = 0;\n\n/**\n * @summary Tab panels are used inside [tab groups](/components/tab-group) to display tabbed content.\n * @documentation https://shoelace.style/components/tab-panel\n * @status stable\n * @since 2.0\n *\n * @slot - The tab panel's content.\n *\n * @csspart base - The component's base wrapper.\n *\n * @cssproperty --padding - The tab panel's padding.\n */\nexport default class SlTabPanel extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  private readonly attrId = ++id;\n  private readonly componentId = `sl-tab-panel-${this.attrId}`;\n\n  /** The tab panel's name. */\n  @property({ reflect: true }) name = '';\n\n  /** When true, the tab panel will be shown. */\n  @property({ type: Boolean, reflect: true }) active = false;\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.id = this.id.length > 0 ? this.id : this.componentId;\n    this.setAttribute('role', 'tabpanel');\n  }\n\n  @watch('active')\n  handleActiveChange() {\n    this.setAttribute('aria-hidden', this.active ? 'false' : 'true');\n  }\n\n  render() {\n    return html`\n      <slot\n        part=\"base\"\n        class=${classMap({\n          'tab-panel': true,\n          'tab-panel--active': this.active\n        })}\n      ></slot>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/tab-panel/tab-panel.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --padding: 0;\n\n    display: none;\n  }\n\n  :host([active]) {\n    display: block;\n  }\n\n  .tab-panel {\n    display: block;\n    padding: var(--padding);\n  }\n`;\n"
  },
  {
    "path": "src/components/tab-panel/tab-panel.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html } from '@open-wc/testing';\nimport type SlTabPanel from './tab-panel.js';\n\ndescribe('<sl-tab-panel>', () => {\n  it('passes accessibility test', async () => {\n    const el = await fixture<SlTabPanel>(html` <sl-tab-panel>Test</sl-tab-panel> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlTabPanel>(html` <sl-tab-panel>Test</sl-tab-panel> `);\n\n    expect(el.id).to.equal('sl-tab-panel-2');\n    expect(el.name).to.equal('');\n    expect(el.active).to.equal(false);\n    expect(el.getAttribute('role')).to.equal('tabpanel');\n    expect(el.getAttribute('aria-hidden')).to.equal('true');\n  });\n\n  it('properties should reflect', async () => {\n    const el = await fixture<SlTabPanel>(html` <sl-tab-panel>Test</sl-tab-panel> `);\n\n    el.name = 'test';\n    el.active = true;\n    await aTimeout(100);\n    expect(el.getAttribute('name')).to.equal('test');\n    expect(el.hasAttribute('active')).to.equal(true);\n  });\n\n  it('changing active should always update aria-hidden role', async () => {\n    const el = await fixture<SlTabPanel>(html` <sl-tab-panel>Test</sl-tab-panel> `);\n\n    el.active = true;\n    await aTimeout(100);\n    expect(el.getAttribute('aria-hidden')).to.equal('false');\n  });\n\n  it('passed id should be used', async () => {\n    const el = await fixture<SlTabPanel>(html` <sl-tab-panel id=\"test-id\">Test</sl-tab-panel> `);\n\n    expect(el.id).to.equal('test-id');\n  });\n});\n"
  },
  {
    "path": "src/components/tab-panel/tab-panel.ts",
    "content": "import SlTabPanel from './tab-panel.component.js';\n\nexport * from './tab-panel.component.js';\nexport default SlTabPanel;\n\nSlTabPanel.define('sl-tab-panel');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tab-panel': SlTabPanel;\n  }\n}\n"
  },
  {
    "path": "src/components/tag/tag.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property } from 'lit/decorators.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlIconButton from '../icon-button/icon-button.component.js';\nimport styles from './tag.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Tags are used as labels to organize things or to indicate a selection.\n * @documentation https://shoelace.style/components/tag\n * @status stable\n * @since 2.0\n *\n * @dependency sl-icon-button\n *\n * @slot - The tag's content.\n *\n * @event sl-remove - Emitted when the remove button is activated.\n *\n * @csspart base - The component's base wrapper.\n * @csspart content - The tag's content.\n * @csspart remove-button - The tag's remove button, an `<sl-icon-button>`.\n * @csspart remove-button__base - The remove button's exported `base` part.\n */\nexport default class SlTag extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-icon-button': SlIconButton };\n\n  private readonly localize = new LocalizeController(this);\n\n  /** The tag's theme variant. */\n  @property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' = 'neutral';\n\n  /** The tag's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Draws a pill-style tag with rounded edges. */\n  @property({ type: Boolean, reflect: true }) pill = false;\n\n  /** Makes the tag removable and shows a remove button. */\n  @property({ type: Boolean }) removable = false;\n\n  private handleRemoveClick() {\n    this.emit('sl-remove');\n  }\n\n  render() {\n    return html`\n      <span\n        part=\"base\"\n        class=${classMap({\n          tag: true,\n\n          // Types\n          'tag--primary': this.variant === 'primary',\n          'tag--success': this.variant === 'success',\n          'tag--neutral': this.variant === 'neutral',\n          'tag--warning': this.variant === 'warning',\n          'tag--danger': this.variant === 'danger',\n          'tag--text': this.variant === 'text',\n\n          // Sizes\n          'tag--small': this.size === 'small',\n          'tag--medium': this.size === 'medium',\n          'tag--large': this.size === 'large',\n\n          // Modifiers\n          'tag--pill': this.pill,\n          'tag--removable': this.removable\n        })}\n      >\n        <slot part=\"content\" class=\"tag__content\"></slot>\n\n        ${this.removable\n          ? html`\n              <sl-icon-button\n                part=\"remove-button\"\n                exportparts=\"base:remove-button__base\"\n                name=\"x-lg\"\n                library=\"system\"\n                label=${this.localize.term('remove')}\n                class=\"tag__remove\"\n                @click=${this.handleRemoveClick}\n                tabindex=\"-1\"\n              ></sl-icon-button>\n            `\n          : ''}\n      </span>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/tag/tag.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: inline-block;\n  }\n\n  .tag {\n    display: flex;\n    align-items: center;\n    border: solid 1px;\n    line-height: 1;\n    white-space: nowrap;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  .tag__remove::part(base) {\n    color: inherit;\n    padding: 0;\n  }\n\n  /*\n   * Variant modifiers\n   */\n\n  .tag--primary {\n    background-color: var(--sl-color-primary-50);\n    border-color: var(--sl-color-primary-200);\n    color: var(--sl-color-primary-800);\n  }\n\n  .tag--primary:active > sl-icon-button {\n    color: var(--sl-color-primary-600);\n  }\n\n  .tag--success {\n    background-color: var(--sl-color-success-50);\n    border-color: var(--sl-color-success-200);\n    color: var(--sl-color-success-800);\n  }\n\n  .tag--success:active > sl-icon-button {\n    color: var(--sl-color-success-600);\n  }\n\n  .tag--neutral {\n    background-color: var(--sl-color-neutral-50);\n    border-color: var(--sl-color-neutral-200);\n    color: var(--sl-color-neutral-800);\n  }\n\n  .tag--neutral:active > sl-icon-button {\n    color: var(--sl-color-neutral-600);\n  }\n\n  .tag--warning {\n    background-color: var(--sl-color-warning-50);\n    border-color: var(--sl-color-warning-200);\n    color: var(--sl-color-warning-800);\n  }\n\n  .tag--warning:active > sl-icon-button {\n    color: var(--sl-color-warning-600);\n  }\n\n  .tag--danger {\n    background-color: var(--sl-color-danger-50);\n    border-color: var(--sl-color-danger-200);\n    color: var(--sl-color-danger-800);\n  }\n\n  .tag--danger:active > sl-icon-button {\n    color: var(--sl-color-danger-600);\n  }\n\n  /*\n   * Size modifiers\n   */\n\n  .tag--small {\n    font-size: var(--sl-button-font-size-small);\n    height: calc(var(--sl-input-height-small) * 0.8);\n    line-height: calc(var(--sl-input-height-small) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-small);\n    padding: 0 var(--sl-spacing-x-small);\n  }\n\n  .tag--medium {\n    font-size: var(--sl-button-font-size-medium);\n    height: calc(var(--sl-input-height-medium) * 0.8);\n    line-height: calc(var(--sl-input-height-medium) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-medium);\n    padding: 0 var(--sl-spacing-small);\n  }\n\n  .tag--large {\n    font-size: var(--sl-button-font-size-large);\n    height: calc(var(--sl-input-height-large) * 0.8);\n    line-height: calc(var(--sl-input-height-large) - var(--sl-input-border-width) * 2);\n    border-radius: var(--sl-input-border-radius-large);\n    padding: 0 var(--sl-spacing-medium);\n  }\n\n  .tag__remove {\n    margin-inline-start: var(--sl-spacing-x-small);\n  }\n\n  /*\n   * Pill modifier\n   */\n\n  .tag--pill {\n    border-radius: var(--sl-border-radius-pill);\n  }\n`;\n"
  },
  {
    "path": "src/components/tag/tag.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlTag from './tag.js';\n\ndescribe('<sl-tag>', () => {\n  it('should render default tag', async () => {\n    const el = await fixture<SlTag>(html` <sl-tag>Test</sl-tag> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(el.getAttribute('size')).to.equal('medium');\n    expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium ');\n  });\n\n  it('should set variant by attribute', async () => {\n    const el = await fixture<SlTag>(html` <sl-tag variant=\"danger\">Test</sl-tag> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' tag tag--danger tag--medium ');\n  });\n\n  it('should set size by attribute', async () => {\n    const el = await fixture<SlTag>(html` <sl-tag size=\"large\">Test</sl-tag> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--large ');\n  });\n\n  it('should set pill-attribute by attribute', async () => {\n    const el = await fixture<SlTag>(html` <sl-tag pill>Test</sl-tag> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n\n    expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium tag--pill ');\n  });\n\n  it('should set removable by attribute', async () => {\n    const el = await fixture<SlTag>(html` <sl-tag removable>Test</sl-tag> `);\n\n    const base = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"base\"]')!;\n    const removeButton = el.shadowRoot!.querySelector('[part~=\"remove-button\"]');\n\n    expect(el.removable).to.equal(true);\n    expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium tag--removable ');\n    expect(removeButton).not.to.be.null;\n  });\n\n  describe('removable', () => {\n    it('should emit remove event when remove button clicked', async () => {\n      const el = await fixture<SlTag>(html` <sl-tag removable>Test</sl-tag> `);\n\n      const removeButton = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~=\"remove-button\"]')!;\n      const spy = sinon.spy();\n\n      el.addEventListener('sl-remove', spy, { once: true });\n\n      removeButton.click();\n\n      expect(spy.called).to.equal(true);\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/tag/tag.ts",
    "content": "import SlTag from './tag.component.js';\n\nexport * from './tag.component.js';\nexport default SlTag;\n\nSlTag.define('sl-tag');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tag': SlTag;\n  }\n}\n"
  },
  {
    "path": "src/components/textarea/textarea.component.ts",
    "content": "import { classMap } from 'lit/directives/class-map.js';\nimport { defaultValue } from '../../internal/default-value.js';\nimport { FormControlController } from '../../internal/form.js';\nimport { HasSlotController } from '../../internal/slot.js';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport formControlStyles from '../../styles/form-control.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './textarea.styles.js';\nimport type { CSSResultGroup } from 'lit';\nimport type { ShoelaceFormControl } from '../../internal/shoelace-element.js';\n\n/**\n * @summary Textareas collect data from the user and allow multiple lines of text.\n * @documentation https://shoelace.style/components/textarea\n * @status stable\n * @since 2.0\n *\n * @slot label - The textarea's label. Alternatively, you can use the `label` attribute.\n * @slot help-text - Text that describes how to use the input. Alternatively, you can use the `help-text` attribute.\n *\n * @event sl-blur - Emitted when the control loses focus.\n * @event sl-change - Emitted when an alteration to the control's value is committed by the user.\n * @event sl-focus - Emitted when the control gains focus.\n * @event sl-input - Emitted when the control receives input.\n * @event sl-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n *\n * @csspart form-control - The form control that wraps the label, input, and help text.\n * @csspart form-control-label - The label's wrapper.\n * @csspart form-control-input - The input's wrapper.\n * @csspart form-control-help-text - The help text's wrapper.\n * @csspart base - The component's base wrapper.\n * @csspart textarea - The internal `<textarea>` control.\n */\nexport default class SlTextarea extends ShoelaceElement implements ShoelaceFormControl {\n  static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];\n\n  private readonly formControlController = new FormControlController(this, {\n    assumeInteractionOn: ['sl-blur', 'sl-input']\n  });\n  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');\n  private resizeObserver: ResizeObserver;\n\n  @query('.textarea__control') input: HTMLTextAreaElement;\n  @query('.textarea__size-adjuster') sizeAdjuster: HTMLTextAreaElement;\n\n  @state() private hasFocus = false;\n  @property() title = ''; // make reactive to pass through\n\n  /** The name of the textarea, submitted as a name/value pair with form data. */\n  @property() name = '';\n\n  /** The current value of the textarea, submitted as a name/value pair with form data. */\n  @property() value = '';\n\n  /** The textarea's size. */\n  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n  /** Draws a filled textarea. */\n  @property({ type: Boolean, reflect: true }) filled = false;\n\n  /** The textarea's label. If you need to display HTML, use the `label` slot instead. */\n  @property() label = '';\n\n  /** The textarea's help text. If you need to display HTML, use the `help-text` slot instead. */\n  @property({ attribute: 'help-text' }) helpText = '';\n\n  /** Placeholder text to show as a hint when the input is empty. */\n  @property() placeholder = '';\n\n  /** The number of rows to display by default. */\n  @property({ type: Number }) rows = 4;\n\n  /** Controls how the textarea can be resized. */\n  @property() resize: 'none' | 'vertical' | 'auto' = 'vertical';\n\n  /** Disables the textarea. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Makes the textarea readonly. */\n  @property({ type: Boolean, reflect: true }) readonly = false;\n\n  /**\n   * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you\n   * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in\n   * the same document or shadow root for this to work.\n   */\n  @property({ reflect: true }) form = '';\n\n  /** Makes the textarea a required field. */\n  @property({ type: Boolean, reflect: true }) required = false;\n\n  /** The minimum length of input that will be considered valid. */\n  @property({ type: Number }) minlength: number;\n\n  /** The maximum length of input that will be considered valid. */\n  @property({ type: Number }) maxlength: number;\n\n  /** Controls whether and how text input is automatically capitalized as it is entered by the user. */\n  @property() autocapitalize: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';\n\n  /** Indicates whether the browser's autocorrect feature is on or off. */\n  @property() autocorrect: string;\n\n  /**\n   * Specifies what permission the browser has to provide assistance in filling out form field values. Refer to\n   * [this page on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for available values.\n   */\n  @property() autocomplete: string;\n\n  /** Indicates that the input should receive focus on page load. */\n  @property({ type: Boolean }) autofocus: boolean;\n\n  /** Used to customize the label or icon of the Enter key on virtual keyboards. */\n  @property() enterkeyhint: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';\n\n  /** Enables spell checking on the textarea. */\n  @property({\n    type: Boolean,\n    converter: {\n      // Allow \"true|false\" attribute values but keep the property boolean\n      fromAttribute: value => (!value || value === 'false' ? false : true),\n      toAttribute: value => (value ? 'true' : 'false')\n    }\n  })\n  spellcheck = true;\n\n  /**\n   * Tells the browser what type of data will be entered by the user, allowing it to display the appropriate virtual\n   * keyboard on supportive devices.\n   */\n  @property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';\n\n  /** The default value of the form control. Primarily used for resetting the form control. */\n  @defaultValue() defaultValue = '';\n\n  /** Gets the validity state object */\n  get validity() {\n    return this.input.validity;\n  }\n\n  /** Gets the validation message */\n  get validationMessage() {\n    return this.input.validationMessage;\n  }\n\n  connectedCallback() {\n    super.connectedCallback();\n    this.resizeObserver = new ResizeObserver(() => this.setTextareaHeight());\n\n    this.updateComplete.then(() => {\n      this.setTextareaHeight();\n      this.resizeObserver.observe(this.input);\n    });\n  }\n\n  firstUpdated() {\n    this.formControlController.updateValidity();\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    if (this.input) {\n      this.resizeObserver?.unobserve(this.input);\n    }\n  }\n\n  private handleBlur() {\n    this.hasFocus = false;\n    this.emit('sl-blur');\n  }\n\n  private handleChange() {\n    this.value = this.input.value;\n    this.setTextareaHeight();\n    this.emit('sl-change');\n  }\n\n  private handleFocus() {\n    this.hasFocus = true;\n    this.emit('sl-focus');\n  }\n\n  private handleInput() {\n    this.value = this.input.value;\n    this.emit('sl-input');\n  }\n\n  private handleInvalid(event: Event) {\n    this.formControlController.setValidity(false);\n    this.formControlController.emitInvalidEvent(event);\n  }\n\n  private setTextareaHeight() {\n    if (this.resize === 'auto') {\n      // This prevents layout shifts. We use `clientHeight` instead of `scrollHeight` to account for if the `<textarea>` has a max-height set on it. In my tests, this has worked fine. Im not aware of any edge cases. [Konnor]\n      this.sizeAdjuster.style.height = `${this.input.clientHeight}px`;\n      this.input.style.height = 'auto';\n      this.input.style.height = `${this.input.scrollHeight}px`;\n    } else {\n      this.input.style.height = '';\n    }\n  }\n\n  @watch('disabled', { waitUntilFirstUpdate: true })\n  handleDisabledChange() {\n    // Disabled form controls are always valid\n    this.formControlController.setValidity(this.disabled);\n  }\n\n  @watch('rows', { waitUntilFirstUpdate: true })\n  handleRowsChange() {\n    this.setTextareaHeight();\n  }\n\n  @watch('value', { waitUntilFirstUpdate: true })\n  async handleValueChange() {\n    await this.updateComplete;\n    this.formControlController.updateValidity();\n    this.setTextareaHeight();\n  }\n\n  /** Sets focus on the textarea. */\n  focus(options?: FocusOptions) {\n    this.input.focus(options);\n  }\n\n  /** Removes focus from the textarea. */\n  blur() {\n    this.input.blur();\n  }\n\n  /** Selects all the text in the textarea. */\n  select() {\n    this.input.select();\n  }\n\n  /** Gets or sets the textarea's scroll position. */\n  scrollPosition(position?: { top?: number; left?: number }): { top: number; left: number } | undefined {\n    if (position) {\n      if (typeof position.top === 'number') this.input.scrollTop = position.top;\n      if (typeof position.left === 'number') this.input.scrollLeft = position.left;\n      return undefined;\n    }\n\n    return {\n      top: this.input.scrollTop,\n      left: this.input.scrollTop\n    };\n  }\n\n  /** Sets the start and end positions of the text selection (0-based). */\n  setSelectionRange(\n    selectionStart: number,\n    selectionEnd: number,\n    selectionDirection: 'forward' | 'backward' | 'none' = 'none'\n  ) {\n    this.input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);\n  }\n\n  /** Replaces a range of text with a new string. */\n  setRangeText(\n    replacement: string,\n    start?: number,\n    end?: number,\n    selectMode: 'select' | 'start' | 'end' | 'preserve' = 'preserve'\n  ) {\n    const selectionStart = start ?? this.input.selectionStart;\n    const selectionEnd = end ?? this.input.selectionEnd;\n\n    this.input.setRangeText(replacement, selectionStart, selectionEnd, selectMode);\n\n    if (this.value !== this.input.value) {\n      this.value = this.input.value;\n      this.setTextareaHeight();\n    }\n  }\n\n  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */\n  checkValidity() {\n    return this.input.checkValidity();\n  }\n\n  /** Gets the associated form, if one exists. */\n  getForm(): HTMLFormElement | null {\n    return this.formControlController.getForm();\n  }\n\n  /** Checks for validity and shows the browser's validation message if the control is invalid. */\n  reportValidity() {\n    return this.input.reportValidity();\n  }\n\n  /** Sets a custom validation message. Pass an empty string to restore validity. */\n  setCustomValidity(message: string) {\n    this.input.setCustomValidity(message);\n    this.formControlController.updateValidity();\n  }\n\n  render() {\n    const hasLabelSlot = this.hasSlotController.test('label');\n    const hasHelpTextSlot = this.hasSlotController.test('help-text');\n    const hasLabel = this.label ? true : !!hasLabelSlot;\n    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;\n\n    return html`\n      <div\n        part=\"form-control\"\n        class=${classMap({\n          'form-control': true,\n          'form-control--small': this.size === 'small',\n          'form-control--medium': this.size === 'medium',\n          'form-control--large': this.size === 'large',\n          'form-control--has-label': hasLabel,\n          'form-control--has-help-text': hasHelpText\n        })}\n      >\n        <label\n          part=\"form-control-label\"\n          class=\"form-control__label\"\n          for=\"input\"\n          aria-hidden=${hasLabel ? 'false' : 'true'}\n        >\n          <slot name=\"label\">${this.label}</slot>\n        </label>\n\n        <div part=\"form-control-input\" class=\"form-control-input\">\n          <div\n            part=\"base\"\n            class=${classMap({\n              textarea: true,\n              'textarea--small': this.size === 'small',\n              'textarea--medium': this.size === 'medium',\n              'textarea--large': this.size === 'large',\n              'textarea--standard': !this.filled,\n              'textarea--filled': this.filled,\n              'textarea--disabled': this.disabled,\n              'textarea--focused': this.hasFocus,\n              'textarea--empty': !this.value,\n              'textarea--resize-none': this.resize === 'none',\n              'textarea--resize-vertical': this.resize === 'vertical',\n              'textarea--resize-auto': this.resize === 'auto'\n            })}\n          >\n            <textarea\n              part=\"textarea\"\n              id=\"input\"\n              class=\"textarea__control\"\n              title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}\n              name=${ifDefined(this.name)}\n              .value=${live(this.value)}\n              ?disabled=${this.disabled}\n              ?readonly=${this.readonly}\n              ?required=${this.required}\n              placeholder=${ifDefined(this.placeholder)}\n              rows=${ifDefined(this.rows)}\n              minlength=${ifDefined(this.minlength)}\n              maxlength=${ifDefined(this.maxlength)}\n              autocapitalize=${ifDefined(this.autocapitalize)}\n              autocorrect=${ifDefined(this.autocorrect)}\n              ?autofocus=${this.autofocus}\n              spellcheck=${ifDefined(this.spellcheck)}\n              enterkeyhint=${ifDefined(this.enterkeyhint)}\n              inputmode=${ifDefined(this.inputmode)}\n              aria-describedby=\"help-text\"\n              @change=${this.handleChange}\n              @input=${this.handleInput}\n              @invalid=${this.handleInvalid}\n              @focus=${this.handleFocus}\n              @blur=${this.handleBlur}\n            ></textarea>\n            <!-- This \"adjuster\" exists to prevent layout shifting. https://github.com/shoelace-style/shoelace/issues/2180 -->\n            <div part=\"textarea-adjuster\" class=\"textarea__size-adjuster\" ?hidden=${this.resize !== 'auto'}></div>\n          </div>\n        </div>\n\n        <div\n          part=\"form-control-help-text\"\n          id=\"help-text\"\n          class=\"form-control__help-text\"\n          aria-hidden=${hasHelpText ? 'false' : 'true'}\n        >\n          <slot name=\"help-text\">${this.helpText}</slot>\n        </div>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/textarea/textarea.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n  }\n\n  .textarea {\n    display: grid;\n    align-items: center;\n    position: relative;\n    width: 100%;\n    font-family: var(--sl-input-font-family);\n    font-weight: var(--sl-input-font-weight);\n    line-height: var(--sl-line-height-normal);\n    letter-spacing: var(--sl-input-letter-spacing);\n    vertical-align: middle;\n    transition:\n      var(--sl-transition-fast) color,\n      var(--sl-transition-fast) border,\n      var(--sl-transition-fast) box-shadow,\n      var(--sl-transition-fast) background-color;\n    cursor: text;\n  }\n\n  /* Standard textareas */\n  .textarea--standard {\n    background-color: var(--sl-input-background-color);\n    border: solid var(--sl-input-border-width) var(--sl-input-border-color);\n  }\n\n  .textarea--standard:hover:not(.textarea--disabled) {\n    background-color: var(--sl-input-background-color-hover);\n    border-color: var(--sl-input-border-color-hover);\n  }\n  .textarea--standard:hover:not(.textarea--disabled) .textarea__control {\n    color: var(--sl-input-color-hover);\n  }\n\n  .textarea--standard.textarea--focused:not(.textarea--disabled) {\n    background-color: var(--sl-input-background-color-focus);\n    border-color: var(--sl-input-border-color-focus);\n    color: var(--sl-input-color-focus);\n    box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);\n  }\n\n  .textarea--standard.textarea--focused:not(.textarea--disabled) .textarea__control {\n    color: var(--sl-input-color-focus);\n  }\n\n  .textarea--standard.textarea--disabled {\n    background-color: var(--sl-input-background-color-disabled);\n    border-color: var(--sl-input-border-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .textarea__control,\n  .textarea__size-adjuster {\n    grid-area: 1 / 1 / 2 / 2;\n  }\n\n  .textarea__size-adjuster {\n    visibility: hidden;\n    pointer-events: none;\n    opacity: 0;\n  }\n\n  .textarea--standard.textarea--disabled .textarea__control {\n    color: var(--sl-input-color-disabled);\n  }\n\n  .textarea--standard.textarea--disabled .textarea__control::placeholder {\n    color: var(--sl-input-placeholder-color-disabled);\n  }\n\n  /* Filled textareas */\n  .textarea--filled {\n    border: none;\n    background-color: var(--sl-input-filled-background-color);\n    color: var(--sl-input-color);\n  }\n\n  .textarea--filled:hover:not(.textarea--disabled) {\n    background-color: var(--sl-input-filled-background-color-hover);\n  }\n\n  .textarea--filled.textarea--focused:not(.textarea--disabled) {\n    background-color: var(--sl-input-filled-background-color-focus);\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n  }\n\n  .textarea--filled.textarea--disabled {\n    background-color: var(--sl-input-filled-background-color-disabled);\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  .textarea__control {\n    font-family: inherit;\n    font-size: inherit;\n    font-weight: inherit;\n    line-height: 1.4;\n    color: var(--sl-input-color);\n    border: none;\n    background: none;\n    box-shadow: none;\n    cursor: inherit;\n    -webkit-appearance: none;\n  }\n\n  .textarea__control::-webkit-search-decoration,\n  .textarea__control::-webkit-search-cancel-button,\n  .textarea__control::-webkit-search-results-button,\n  .textarea__control::-webkit-search-results-decoration {\n    -webkit-appearance: none;\n  }\n\n  .textarea__control::placeholder {\n    color: var(--sl-input-placeholder-color);\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  .textarea__control:focus {\n    outline: none;\n  }\n\n  /*\n   * Size modifiers\n   */\n\n  .textarea--small {\n    border-radius: var(--sl-input-border-radius-small);\n    font-size: var(--sl-input-font-size-small);\n  }\n\n  .textarea--small .textarea__control {\n    padding: 0.5em var(--sl-input-spacing-small);\n  }\n\n  .textarea--medium {\n    border-radius: var(--sl-input-border-radius-medium);\n    font-size: var(--sl-input-font-size-medium);\n  }\n\n  .textarea--medium .textarea__control {\n    padding: 0.5em var(--sl-input-spacing-medium);\n  }\n\n  .textarea--large {\n    border-radius: var(--sl-input-border-radius-large);\n    font-size: var(--sl-input-font-size-large);\n  }\n\n  .textarea--large .textarea__control {\n    padding: 0.5em var(--sl-input-spacing-large);\n  }\n\n  /*\n   * Resize types\n   */\n\n  .textarea--resize-none .textarea__control {\n    resize: none;\n  }\n\n  .textarea--resize-vertical .textarea__control {\n    resize: vertical;\n  }\n\n  .textarea--resize-auto .textarea__control {\n    height: auto;\n    resize: none;\n    overflow-y: hidden;\n  }\n`;\n"
  },
  {
    "path": "src/components/textarea/textarea.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport { serialize } from '../../utilities/form.js';\nimport sinon from 'sinon';\nimport type SlTextarea from './textarea.js';\n\ndescribe('<sl-textarea>', () => {\n  it('should pass accessibility tests', async () => {\n    const el = await fixture<SlTextarea>(html` <sl-textarea label=\"Name\"></sl-textarea> `);\n    await expect(el).to.be.accessible();\n  });\n\n  it('default properties', async () => {\n    const el = await fixture<SlTextarea>(html` <sl-textarea></sl-textarea> `);\n\n    expect(el.size).to.equal('medium');\n    expect(el.name).to.equal('');\n    expect(el.value).to.equal('');\n    expect(el.defaultValue).to.equal('');\n    expect(el.title).to.equal('');\n    expect(el.filled).to.be.false;\n    expect(el.label).to.equal('');\n    expect(el.helpText).to.equal('');\n    expect(el.placeholder).to.equal('');\n    expect(el.rows).to.equal(4);\n    expect(el.resize).to.equal('vertical');\n    expect(el.disabled).to.be.false;\n    expect(el.readonly).to.be.false;\n    expect(el.minlength).to.be.undefined;\n    expect(el.maxlength).to.be.undefined;\n    expect(el.required).to.be.false;\n    expect(el.autocapitalize).to.be.undefined;\n    expect(el.autocorrect).to.be.undefined;\n    expect(el.autocomplete).to.be.undefined;\n    expect(el.autofocus).to.be.undefined;\n    expect(el.enterkeyhint).to.be.undefined;\n    expect(el.spellcheck).to.be.true;\n    expect(el.inputmode).to.be.undefined;\n  });\n\n  it('should have title if title attribute is set', async () => {\n    const el = await fixture<SlTextarea>(html` <sl-textarea title=\"Test\"></sl-textarea> `);\n    const textarea = el.shadowRoot!.querySelector('textarea')!;\n\n    expect(textarea.title).to.equal('Test');\n  });\n\n  it('should be disabled with the disabled attribute', async () => {\n    const el = await fixture<SlTextarea>(html` <sl-textarea disabled></sl-textarea> `);\n    const textarea = el.shadowRoot!.querySelector<HTMLTextAreaElement>('[part~=\"textarea\"]')!;\n\n    expect(textarea.disabled).to.be.true;\n  });\n\n  it('should focus the textarea when clicking on the label', async () => {\n    const el = await fixture<SlTextarea>(html` <sl-textarea label=\"Name\"></sl-textarea> `);\n    const label = el.shadowRoot!.querySelector('[part~=\"form-control-label\"]')!;\n    const submitHandler = sinon.spy();\n\n    el.addEventListener('sl-focus', submitHandler);\n    (label as HTMLLabelElement).click();\n    await waitUntil(() => submitHandler.calledOnce);\n\n    expect(submitHandler).to.have.been.calledOnce;\n  });\n\n  describe('when the value changes', () => {\n    it('should emit sl-change and sl-input when the user types in the textarea', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea></sl-textarea> `);\n      const inputHandler = sinon.spy();\n      const changeHandler = sinon.spy();\n\n      el.addEventListener('sl-input', inputHandler);\n      el.addEventListener('sl-change', changeHandler);\n      el.focus();\n      await sendKeys({ type: 'abc' });\n      el.blur();\n      await el.updateComplete;\n\n      expect(changeHandler).to.have.been.calledOnce;\n      expect(inputHandler).to.have.been.calledThrice;\n    });\n\n    it('should not emit sl-change or sl-input when the value is set programmatically', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea></sl-textarea> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.value = 'abc';\n\n      await el.updateComplete;\n    });\n\n    it('should not emit sl-change or sl-input when calling setRangeText()', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea value=\"hi there\"></sl-textarea> `);\n\n      el.addEventListener('sl-change', () => expect.fail('sl-change should not be emitted'));\n      el.addEventListener('sl-input', () => expect.fail('sl-input should not be emitted'));\n      el.focus();\n      el.setSelectionRange(0, 2);\n      el.setRangeText('hello');\n\n      await el.updateComplete;\n    });\n  });\n\n  describe('when using constraint validation', () => {\n    it('should be valid by default', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea></sl-textarea> `);\n\n      expect(el.checkValidity()).to.be.true;\n    });\n\n    it('should be invalid when required and empty', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea required></sl-textarea> `);\n\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should be invalid when required and after removing disabled ', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea disabled required></sl-textarea> `);\n\n      el.disabled = false;\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should be invalid when required and disabled is removed', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea disabled required></sl-textarea> `);\n      el.disabled = false;\n      await el.updateComplete;\n      expect(el.checkValidity()).to.be.false;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when valid', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea required value=\"a\"></sl-textarea> `);\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.false;\n      expect(el.hasAttribute('data-valid')).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      el.focus();\n      await sendKeys({ press: 'b' });\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.checkValidity()).to.be.true;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.true;\n    });\n\n    it('should receive the correct validation attributes (\"states\") when invalid', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea required></sl-textarea> `);\n\n      expect(el.hasAttribute('data-required')).to.be.true;\n      expect(el.hasAttribute('data-optional')).to.be.false;\n      expect(el.hasAttribute('data-invalid')).to.be.true;\n      expect(el.hasAttribute('data-valid')).to.be.false;\n      expect(el.hasAttribute('data-user-invalid')).to.be.false;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n\n      el.focus();\n      await sendKeys({ press: 'a' });\n      await sendKeys({ press: 'Backspace' });\n      await el.updateComplete;\n      el.blur();\n      await el.updateComplete;\n\n      expect(el.hasAttribute('data-user-invalid')).to.be.true;\n      expect(el.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should receive validation attributes (\"states\") even when novalidate is used on the parent form', async () => {\n      const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-textarea required></sl-textarea></form> `);\n      const textarea = el.querySelector<SlTextarea>('sl-textarea')!;\n\n      expect(textarea.hasAttribute('data-required')).to.be.true;\n      expect(textarea.hasAttribute('data-optional')).to.be.false;\n      expect(textarea.hasAttribute('data-invalid')).to.be.true;\n      expect(textarea.hasAttribute('data-valid')).to.be.false;\n      expect(textarea.hasAttribute('data-user-invalid')).to.be.false;\n      expect(textarea.hasAttribute('data-user-valid')).to.be.false;\n    });\n  });\n\n  describe('when submitting a form', () => {\n    it('should serialize its name and value with FormData', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-textarea name=\"a\" value=\"1\"></sl-textarea></form> `);\n      const formData = new FormData(form);\n      expect(formData.get('a')).to.equal('1');\n    });\n\n    it('should serialize its name and value with JSON', async () => {\n      const form = await fixture<HTMLFormElement>(html` <form><sl-textarea name=\"a\" value=\"1\"></sl-textarea></form> `);\n      const json = serialize(form);\n      expect(json.a).to.equal('1');\n    });\n\n    it('should be invalid when setCustomValidity() is called with a non-empty value', async () => {\n      const textarea = await fixture<HTMLFormElement>(html` <sl-textarea></sl-textarea> `);\n\n      textarea.setCustomValidity('Invalid selection');\n      await textarea.updateComplete;\n\n      expect(textarea.checkValidity()).to.be.false;\n      expect(textarea.hasAttribute('data-invalid')).to.be.true;\n      expect(textarea.hasAttribute('data-valid')).to.be.false;\n      expect(textarea.hasAttribute('data-user-invalid')).to.be.false;\n      expect(textarea.hasAttribute('data-user-valid')).to.be.false;\n\n      textarea.focus();\n      await sendKeys({ type: 'test' });\n      await textarea.updateComplete;\n      textarea.blur();\n      await textarea.updateComplete;\n\n      expect(textarea.hasAttribute('data-user-invalid')).to.be.true;\n      expect(textarea.hasAttribute('data-user-valid')).to.be.false;\n    });\n\n    it('should be present in form data when using the form attribute and located outside of a <form>', async () => {\n      const el = await fixture<HTMLFormElement>(html`\n        <div>\n          <form id=\"f\">\n            <sl-button type=\"submit\">Submit</sl-button>\n          </form>\n          <sl-textarea form=\"f\" name=\"a\" value=\"1\"></sl-textarea>\n        </div>\n      `);\n      const form = el.querySelector('form')!;\n      const formData = new FormData(form);\n\n      expect(formData.get('a')).to.equal('1');\n    });\n  });\n\n  describe('when resetting a form', () => {\n    it('should reset the element to its initial value', async () => {\n      const form = await fixture<HTMLFormElement>(html`\n        <form>\n          <sl-textarea name=\"a\" value=\"test\"></sl-textarea>\n          <sl-button type=\"reset\">Reset</sl-button>\n        </form>\n      `);\n      const button = form.querySelector('sl-button')!;\n      const textarea = form.querySelector('sl-textarea')!;\n      textarea.value = '1234';\n\n      await textarea.updateComplete;\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await textarea.updateComplete;\n\n      expect(textarea.value).to.equal('test');\n\n      textarea.defaultValue = '';\n\n      setTimeout(() => button.click());\n      await oneEvent(form, 'reset');\n      await textarea.updateComplete;\n\n      expect(textarea.value).to.equal('');\n    });\n  });\n\n  describe('when using spellcheck', () => {\n    it('should enable spellcheck when no attribute is present', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea></sl-textarea> `);\n      const textarea = el.shadowRoot!.querySelector<HTMLTextAreaElement>('textarea')!;\n      expect(textarea.getAttribute('spellcheck')).to.equal('true');\n      expect(textarea.spellcheck).to.be.true;\n    });\n\n    it('should enable spellcheck when set to \"true\"', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea spellcheck=\"true\"></sl-textarea> `);\n      const textarea = el.shadowRoot!.querySelector<HTMLTextAreaElement>('textarea')!;\n      expect(textarea.getAttribute('spellcheck')).to.equal('true');\n      expect(textarea.spellcheck).to.be.true;\n    });\n\n    it('should disable spellcheck when set to \"false\"', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea spellcheck=\"false\"></sl-textarea> `);\n      const textarea = el.shadowRoot!.querySelector<HTMLTextAreaElement>('textarea')!;\n      expect(textarea.getAttribute('spellcheck')).to.equal('false');\n      expect(textarea.spellcheck).to.be.false;\n    });\n  });\n\n  describe('when using the setRangeText() function', () => {\n    it('should set replacement text in the correct location', async () => {\n      const el = await fixture<SlTextarea>(html` <sl-textarea value=\"test\"></sl-textarea> `);\n\n      el.focus();\n      el.setSelectionRange(1, 3);\n      el.setRangeText('boom');\n      await el.updateComplete;\n      expect(el.value).to.equal('tboomt'); // cspell:disable-line\n    });\n  });\n\n  runFormControlBaseTests('sl-textarea');\n});\n"
  },
  {
    "path": "src/components/textarea/textarea.ts",
    "content": "import SlTextarea from './textarea.component.js';\n\nexport * from './textarea.component.js';\nexport default SlTextarea;\n\nSlTextarea.define('sl-textarea');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-textarea': SlTextarea;\n  }\n}\n"
  },
  {
    "path": "src/components/tooltip/tooltip.component.ts",
    "content": "import { animateTo, parseDuration, stopAnimations } from '../../internal/animate.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { waitForEvent } from '../../internal/event.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlPopup from '../popup/popup.component.js';\nimport styles from './tooltip.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary Tooltips display additional information based on a specific action.\n * @documentation https://shoelace.style/components/tooltip\n * @status stable\n * @since 2.0\n *\n * @dependency sl-popup\n *\n * @slot - The tooltip's target element. Avoid slotting in more than one element, as subsequent ones will be ignored.\n * @slot content - The content to render in the tooltip. Alternatively, you can use the `content` attribute.\n *\n * @event sl-show - Emitted when the tooltip begins to show.\n * @event sl-after-show - Emitted after the tooltip has shown and all animations are complete.\n * @event sl-hide - Emitted when the tooltip begins to hide.\n * @event sl-after-hide - Emitted after the tooltip has hidden and all animations are complete.\n *\n * @csspart base - The component's base wrapper, an `<sl-popup>` element.\n * @csspart base__popup - The popup's exported `popup` part. Use this to target the tooltip's popup container.\n * @csspart base__arrow - The popup's exported `arrow` part. Use this to target the tooltip's arrow.\n * @csspart body - The tooltip's body where its content is rendered.\n *\n * @cssproperty --max-width - The maximum width of the tooltip before its content will wrap.\n * @cssproperty --hide-delay - The amount of time to wait before hiding the tooltip when hovering.\n * @cssproperty --show-delay - The amount of time to wait before showing the tooltip when hovering.\n *\n * @animation tooltip.show - The animation to use when showing the tooltip.\n * @animation tooltip.hide - The animation to use when hiding the tooltip.\n */\nexport default class SlTooltip extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = { 'sl-popup': SlPopup };\n\n  private hoverTimeout: number;\n  private readonly localize = new LocalizeController(this);\n  private closeWatcher: CloseWatcher | null;\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n  @query('.tooltip__body') body: HTMLElement;\n  @query('sl-popup') popup: SlPopup;\n\n  /** The tooltip's content. If you need to display HTML, use the `content` slot instead. */\n  @property() content = '';\n\n  /**\n   * The preferred placement of the tooltip. Note that the actual placement may vary as needed to keep the tooltip\n   * inside of the viewport.\n   */\n  @property() placement:\n    | 'top'\n    | 'top-start'\n    | 'top-end'\n    | 'right'\n    | 'right-start'\n    | 'right-end'\n    | 'bottom'\n    | 'bottom-start'\n    | 'bottom-end'\n    | 'left'\n    | 'left-start'\n    | 'left-end' = 'top';\n\n  /** Disables the tooltip so it won't show when triggered. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** The distance in pixels from which to offset the tooltip away from its target. */\n  @property({ type: Number }) distance = 8;\n\n  /** Indicates whether or not the tooltip is open. You can use this in lieu of the show/hide methods. */\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  /** The distance in pixels from which to offset the tooltip along its target. */\n  @property({ type: Number }) skidding = 0;\n\n  /**\n   * Controls how the tooltip is activated. Possible options include `click`, `hover`, `focus`, and `manual`. Multiple\n   * options can be passed by separating them with a space. When manual is used, the tooltip must be activated\n   * programmatically.\n   */\n  @property() trigger = 'hover focus';\n\n  /**\n   * Enable this option to prevent the tooltip from being clipped when the component is placed inside a container with\n   * `overflow: auto|hidden|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all,\n   * scenarios.\n   */\n  @property({ type: Boolean }) hoist = false;\n\n  constructor() {\n    super();\n    this.addEventListener('blur', this.handleBlur, true);\n    this.addEventListener('focus', this.handleFocus, true);\n    this.addEventListener('click', this.handleClick);\n    this.addEventListener('mouseover', this.handleMouseOver);\n    this.addEventListener('mouseout', this.handleMouseOut);\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    // Cleanup this event in case the tooltip is removed while open\n    this.closeWatcher?.destroy();\n    document.removeEventListener('keydown', this.handleDocumentKeyDown);\n  }\n\n  firstUpdated() {\n    this.body.hidden = !this.open;\n\n    // If the tooltip is visible on init, update its position\n    if (this.open) {\n      this.popup.active = true;\n      this.popup.reposition();\n    }\n  }\n\n  private handleBlur = () => {\n    if (this.hasTrigger('focus')) {\n      this.hide();\n    }\n  };\n\n  private handleClick = () => {\n    if (this.hasTrigger('click')) {\n      if (this.open) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n  };\n\n  private handleFocus = () => {\n    if (this.hasTrigger('focus')) {\n      this.show();\n    }\n  };\n\n  private handleDocumentKeyDown = (event: KeyboardEvent) => {\n    // Pressing escape when a tooltip is open should dismiss it\n    if (event.key === 'Escape') {\n      event.stopPropagation();\n      this.hide();\n    }\n  };\n\n  private handleMouseOver = () => {\n    if (this.hasTrigger('hover')) {\n      const delay = parseDuration(getComputedStyle(this).getPropertyValue('--show-delay'));\n      clearTimeout(this.hoverTimeout);\n      this.hoverTimeout = window.setTimeout(() => this.show(), delay);\n    }\n  };\n\n  private handleMouseOut = () => {\n    if (this.hasTrigger('hover')) {\n      const delay = parseDuration(getComputedStyle(this).getPropertyValue('--hide-delay'));\n      clearTimeout(this.hoverTimeout);\n      this.hoverTimeout = window.setTimeout(() => this.hide(), delay);\n    }\n  };\n\n  private hasTrigger(triggerType: string) {\n    const triggers = this.trigger.split(' ');\n    return triggers.includes(triggerType);\n  }\n\n  @watch('open', { waitUntilFirstUpdate: true })\n  async handleOpenChange() {\n    if (this.open) {\n      if (this.disabled) {\n        return;\n      }\n\n      // Show\n      this.emit('sl-show');\n      if ('CloseWatcher' in window) {\n        this.closeWatcher?.destroy();\n        this.closeWatcher = new CloseWatcher();\n        this.closeWatcher.onclose = () => {\n          this.hide();\n        };\n      } else {\n        document.addEventListener('keydown', this.handleDocumentKeyDown);\n      }\n\n      await stopAnimations(this.body);\n      this.body.hidden = false;\n      this.popup.active = true;\n      const { keyframes, options } = getAnimation(this, 'tooltip.show', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n      this.popup.reposition();\n\n      this.emit('sl-after-show');\n    } else {\n      // Hide\n      this.emit('sl-hide');\n      this.closeWatcher?.destroy();\n      document.removeEventListener('keydown', this.handleDocumentKeyDown);\n\n      await stopAnimations(this.body);\n      const { keyframes, options } = getAnimation(this, 'tooltip.hide', { dir: this.localize.dir() });\n      await animateTo(this.popup.popup, keyframes, options);\n      this.popup.active = false;\n      this.body.hidden = true;\n\n      this.emit('sl-after-hide');\n    }\n  }\n\n  @watch(['content', 'distance', 'hoist', 'placement', 'skidding'])\n  async handleOptionsChange() {\n    if (this.hasUpdated) {\n      await this.updateComplete;\n      this.popup.reposition();\n    }\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    if (this.disabled && this.open) {\n      this.hide();\n    }\n  }\n\n  /** Shows the tooltip. */\n  async show() {\n    if (this.open) {\n      return undefined;\n    }\n\n    this.open = true;\n    return waitForEvent(this, 'sl-after-show');\n  }\n\n  /** Hides the tooltip */\n  async hide() {\n    if (!this.open) {\n      return undefined;\n    }\n\n    this.open = false;\n    return waitForEvent(this, 'sl-after-hide');\n  }\n\n  //\n  // NOTE: Tooltip is a bit unique in that we're using aria-live instead of aria-labelledby to trick screen readers into\n  // announcing the content. It works really well, but it violates an accessibility rule. We're also adding the\n  // aria-describedby attribute to a slot, which is required by <sl-popup> to correctly locate the first assigned\n  // element, otherwise positioning is incorrect.\n  //\n  render() {\n    return html`\n      <sl-popup\n        part=\"base\"\n        exportparts=\"\n          popup:base__popup,\n          arrow:base__arrow\n        \"\n        class=${classMap({\n          tooltip: true,\n          'tooltip--open': this.open\n        })}\n        placement=${this.placement}\n        distance=${this.distance}\n        skidding=${this.skidding}\n        strategy=${this.hoist ? 'fixed' : 'absolute'}\n        flip\n        shift\n        arrow\n        hover-bridge\n      >\n        ${'' /* eslint-disable-next-line lit-a11y/no-aria-slot */}\n        <slot slot=\"anchor\" aria-describedby=\"tooltip\"></slot>\n\n        ${'' /* eslint-disable-next-line lit-a11y/accessible-name */}\n        <div part=\"body\" id=\"tooltip\" class=\"tooltip__body\" role=\"tooltip\" aria-live=${this.open ? 'polite' : 'off'}>\n          <slot name=\"content\">${this.content}</slot>\n        </div>\n      </sl-popup>\n    `;\n  }\n}\n\nsetDefaultAnimation('tooltip.show', {\n  keyframes: [\n    { opacity: 0, scale: 0.8 },\n    { opacity: 1, scale: 1 }\n  ],\n  options: { duration: 150, easing: 'ease' }\n});\n\nsetDefaultAnimation('tooltip.hide', {\n  keyframes: [\n    { opacity: 1, scale: 1 },\n    { opacity: 0, scale: 0.8 }\n  ],\n  options: { duration: 150, easing: 'ease' }\n});\n"
  },
  {
    "path": "src/components/tooltip/tooltip.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    --max-width: 20rem;\n    --hide-delay: 0ms;\n    --show-delay: 150ms;\n\n    display: contents;\n  }\n\n  .tooltip {\n    --arrow-size: var(--sl-tooltip-arrow-size);\n    --arrow-color: var(--sl-tooltip-background-color);\n  }\n\n  .tooltip::part(popup) {\n    z-index: var(--sl-z-index-tooltip);\n  }\n\n  .tooltip[placement^='top']::part(popup) {\n    transform-origin: bottom;\n  }\n\n  .tooltip[placement^='bottom']::part(popup) {\n    transform-origin: top;\n  }\n\n  .tooltip[placement^='left']::part(popup) {\n    transform-origin: right;\n  }\n\n  .tooltip[placement^='right']::part(popup) {\n    transform-origin: left;\n  }\n\n  .tooltip__body {\n    display: block;\n    width: max-content;\n    max-width: var(--max-width);\n    border-radius: var(--sl-tooltip-border-radius);\n    background-color: var(--sl-tooltip-background-color);\n    font-family: var(--sl-tooltip-font-family);\n    font-size: var(--sl-tooltip-font-size);\n    font-weight: var(--sl-tooltip-font-weight);\n    line-height: var(--sl-tooltip-line-height);\n    text-align: start;\n    white-space: normal;\n    color: var(--sl-tooltip-color);\n    padding: var(--sl-tooltip-padding);\n    pointer-events: none;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n`;\n"
  },
  {
    "path": "src/components/tooltip/tooltip.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlTooltip from './tooltip.js';\n\ndescribe('<sl-tooltip>', () => {\n  it('should be visible with the open attribute', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n\n    expect(body.hidden).to.be.false;\n  });\n\n  it('should not be visible without the open attribute', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\">\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n\n    expect(body.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when calling show()', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\">\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.show();\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when calling hide()', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.hide();\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.true;\n  });\n\n  it('should emit sl-show and sl-after-show when setting open = true', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\">\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    const showHandler = sinon.spy();\n    const afterShowHandler = sinon.spy();\n\n    el.addEventListener('sl-show', showHandler);\n    el.addEventListener('sl-after-show', afterShowHandler);\n    el.open = true;\n\n    await waitUntil(() => showHandler.calledOnce);\n    await waitUntil(() => afterShowHandler.calledOnce);\n\n    expect(showHandler).to.have.been.calledOnce;\n    expect(afterShowHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.false;\n  });\n\n  it('should emit sl-hide and sl-after-hide when setting open = false', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.open = false;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.true;\n  });\n\n  it('should hide the tooltip when tooltip is visible and disabled becomes true', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    const hideHandler = sinon.spy();\n    const afterHideHandler = sinon.spy();\n\n    el.addEventListener('sl-hide', hideHandler);\n    el.addEventListener('sl-after-hide', afterHideHandler);\n    el.disabled = true;\n\n    await waitUntil(() => hideHandler.calledOnce);\n    await waitUntil(() => afterHideHandler.calledOnce);\n\n    expect(hideHandler).to.have.been.calledOnce;\n    expect(afterHideHandler).to.have.been.calledOnce;\n    expect(body.hidden).to.be.true;\n  });\n\n  it('should show when open initially', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const body = el.shadowRoot!.querySelector<HTMLElement>('[part~=\"body\"]')!;\n    await el.updateComplete;\n\n    expect(body.hidden).to.be.false;\n  });\n\n  it('should not accept user selection on the tooltip', async () => {\n    const el = await fixture<SlTooltip>(html`\n      <sl-tooltip content=\"This is a tooltip\" open>\n        <sl-button>Hover Me</sl-button>\n      </sl-tooltip>\n    `);\n    const tooltipBody = el.shadowRoot!.querySelector('.tooltip__body')!;\n    const userSelect = getComputedStyle(tooltipBody).userSelect || getComputedStyle(tooltipBody).webkitUserSelect;\n\n    expect(userSelect).to.equal('none');\n  });\n});\n"
  },
  {
    "path": "src/components/tooltip/tooltip.ts",
    "content": "import SlTooltip from './tooltip.component.js';\n\nexport * from './tooltip.component.js';\nexport default SlTooltip;\n\nSlTooltip.define('sl-tooltip');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tooltip': SlTooltip;\n  }\n}\n"
  },
  {
    "path": "src/components/tree/tree.component.ts",
    "content": "import { clamp } from '../../internal/math.js';\nimport { html } from 'lit';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlTreeItem from '../tree-item/tree-item.component.js';\nimport styles from './tree.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\nfunction syncCheckboxes(changedTreeItem: SlTreeItem, initialSync = false) {\n  function syncParentItem(treeItem: SlTreeItem) {\n    const children = treeItem.getChildrenItems({ includeDisabled: false });\n\n    if (children.length) {\n      const allChecked = children.every(item => item.selected);\n      const allUnchecked = children.every(item => !item.selected && !item.indeterminate);\n\n      treeItem.selected = allChecked;\n      treeItem.indeterminate = !allChecked && !allUnchecked;\n    }\n  }\n\n  function syncAncestors(treeItem: SlTreeItem) {\n    const parentItem: SlTreeItem | null = treeItem.parentElement as SlTreeItem;\n\n    if (SlTreeItem.isTreeItem(parentItem)) {\n      syncParentItem(parentItem);\n      syncAncestors(parentItem);\n    }\n  }\n\n  function syncDescendants(treeItem: SlTreeItem) {\n    for (const childItem of treeItem.getChildrenItems()) {\n      childItem.selected = initialSync\n        ? treeItem.selected || childItem.selected\n        : !childItem.disabled && treeItem.selected;\n\n      syncDescendants(childItem);\n    }\n\n    if (initialSync) {\n      syncParentItem(treeItem);\n    }\n  }\n\n  syncDescendants(changedTreeItem);\n  syncAncestors(changedTreeItem);\n}\n\n/**\n * @summary Trees allow you to display a hierarchical list of selectable [tree items](/components/tree-item). Items with children can be expanded and collapsed as desired by the user.\n * @documentation https://shoelace.style/components/tree\n * @status stable\n * @since 2.0\n *\n * @event {{ selection: SlTreeItem[] }} sl-selection-change - Emitted when a tree item is selected or deselected.\n *\n * @slot - The default slot.\n * @slot expand-icon - The icon to show when the tree item is expanded. Works best with `<sl-icon>`.\n * @slot collapse-icon - The icon to show when the tree item is collapsed. Works best with `<sl-icon>`.\n *\n * @csspart base - The component's base wrapper.\n *\n * @cssproperty [--indent-size=var(--sl-spacing-medium)] - The size of the indentation for nested items.\n * @cssproperty [--indent-guide-color=var(--sl-color-neutral-200)] - The color of the indentation line.\n * @cssproperty [--indent-guide-offset=0] - The amount of vertical spacing to leave between the top and bottom of the\n *  indentation line's starting position.\n * @cssproperty [--indent-guide-style=solid] - The style of the indentation line, e.g. solid, dotted, dashed.\n * @cssproperty [--indent-guide-width=0] - The width of the indentation line.\n */\nexport default class SlTree extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n  @query('slot[name=expand-icon]') expandedIconSlot: HTMLSlotElement;\n  @query('slot[name=collapse-icon]') collapsedIconSlot: HTMLSlotElement;\n\n  /**\n   * The selection behavior of the tree. Single selection allows only one node to be selected at a time. Multiple\n   * displays checkboxes and allows more than one node to be selected. Leaf allows only leaf nodes to be selected.\n   */\n  @property() selection: 'single' | 'multiple' | 'leaf' = 'single';\n\n  //\n  // A collection of all the items in the tree, in the order they appear. The collection is live, meaning it is\n  // automatically updated when the underlying document is changed.\n  //\n  private lastFocusedItem: SlTreeItem | null;\n  private mutationObserver: MutationObserver;\n  private clickTarget: SlTreeItem | null = null;\n  private readonly localize = new LocalizeController(this);\n\n  constructor() {\n    super();\n    this.addEventListener('focusin', this.handleFocusIn);\n    this.addEventListener('focusout', this.handleFocusOut);\n    this.addEventListener('sl-lazy-change', this.handleSlotChange);\n  }\n\n  async connectedCallback() {\n    super.connectedCallback();\n\n    this.setAttribute('role', 'tree');\n    this.setAttribute('tabindex', '0');\n\n    await this.updateComplete;\n\n    this.mutationObserver = new MutationObserver(this.handleTreeChanged);\n    this.mutationObserver.observe(this, { childList: true, subtree: true });\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    this.mutationObserver?.disconnect();\n  }\n\n  // Generates a clone of the expand icon element to use for each tree item\n  private getExpandButtonIcon(status: 'expand' | 'collapse') {\n    const slot = status === 'expand' ? this.expandedIconSlot : this.collapsedIconSlot;\n    const icon = slot.assignedElements({ flatten: true })[0] as HTMLElement;\n\n    // Clone it, remove ids, and slot it\n    if (icon) {\n      const clone = icon.cloneNode(true) as HTMLElement;\n      [clone, ...clone.querySelectorAll('[id]')].forEach(el => el.removeAttribute('id'));\n      clone.setAttribute('data-default', '');\n      clone.slot = `${status}-icon`;\n\n      return clone;\n    }\n\n    return null;\n  }\n\n  // Initializes new items by setting the `selectable` property and the expanded/collapsed icons if any\n  private initTreeItem = (item: SlTreeItem) => {\n    item.selectable = this.selection === 'multiple';\n\n    ['expand', 'collapse']\n      .filter(status => !!this.querySelector(`[slot=\"${status}-icon\"]`))\n      .forEach((status: 'expand' | 'collapse') => {\n        const existingIcon = item.querySelector(`[slot=\"${status}-icon\"]`);\n\n        const expandButtonIcon = this.getExpandButtonIcon(status);\n\n        if (!expandButtonIcon) return;\n\n        if (existingIcon === null) {\n          // No separator exists, add one\n          item.append(expandButtonIcon);\n        } else if (existingIcon.hasAttribute('data-default')) {\n          // A default separator exists, replace it\n          existingIcon.replaceWith(expandButtonIcon);\n        } else {\n          // The user provided a custom icon, leave it alone\n        }\n      });\n  };\n\n  private handleTreeChanged = (mutations: MutationRecord[]) => {\n    for (const mutation of mutations) {\n      const addedNodes: SlTreeItem[] = [...mutation.addedNodes].filter(SlTreeItem.isTreeItem) as SlTreeItem[];\n      const removedNodes = [...mutation.removedNodes].filter(SlTreeItem.isTreeItem) as SlTreeItem[];\n\n      addedNodes.forEach(this.initTreeItem);\n\n      if (this.lastFocusedItem && removedNodes.includes(this.lastFocusedItem)) {\n        this.lastFocusedItem = null;\n      }\n    }\n  };\n\n  private selectItem(selectedItem: SlTreeItem) {\n    const previousSelection = [...this.selectedItems];\n\n    if (this.selection === 'multiple') {\n      selectedItem.selected = !selectedItem.selected;\n      if (selectedItem.lazy) {\n        selectedItem.expanded = true;\n      }\n      syncCheckboxes(selectedItem);\n    } else if (this.selection === 'single' || selectedItem.isLeaf) {\n      const items = this.getAllTreeItems();\n      for (const item of items) {\n        item.selected = item === selectedItem;\n      }\n    } else if (this.selection === 'leaf') {\n      selectedItem.expanded = !selectedItem.expanded;\n    }\n\n    const nextSelection = this.selectedItems;\n\n    if (\n      previousSelection.length !== nextSelection.length ||\n      nextSelection.some(item => !previousSelection.includes(item))\n    ) {\n      // Wait for the tree items' DOM to update before emitting\n      Promise.all(nextSelection.map(el => el.updateComplete)).then(() => {\n        this.emit('sl-selection-change', { detail: { selection: nextSelection } });\n      });\n    }\n  }\n\n  private getAllTreeItems() {\n    return [...this.querySelectorAll<SlTreeItem>('sl-tree-item')];\n  }\n\n  private focusItem(item?: SlTreeItem | null) {\n    item?.focus();\n  }\n\n  private handleKeyDown(event: KeyboardEvent) {\n    // Ignore key presses we aren't interested in\n    if (!['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' '].includes(event.key)) {\n      return;\n    }\n\n    // Ignore key presses when focus is inside a text field. This prevents the component from hijacking nested form\n    // controls that exist inside tree items.\n    if (event.composedPath().some((el: HTMLElement) => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))) {\n      return;\n    }\n\n    const items = this.getFocusableItems();\n    const isLtr = this.localize.dir() === 'ltr';\n    const isRtl = this.localize.dir() === 'rtl';\n\n    if (items.length > 0) {\n      event.preventDefault();\n      const activeItemIndex = items.findIndex(item => item.matches(':focus'));\n      const activeItem: SlTreeItem | undefined = items[activeItemIndex];\n\n      const focusItemAt = (index: number) => {\n        const item = items[clamp(index, 0, items.length - 1)];\n        this.focusItem(item);\n      };\n      const toggleExpand = (expanded: boolean) => {\n        activeItem.expanded = expanded;\n      };\n\n      if (event.key === 'ArrowDown') {\n        // Moves focus to the next node that is focusable without opening or closing a node.\n        focusItemAt(activeItemIndex + 1);\n      } else if (event.key === 'ArrowUp') {\n        // Moves focus to the next node that is focusable without opening or closing a node.\n        focusItemAt(activeItemIndex - 1);\n      } else if ((isLtr && event.key === 'ArrowRight') || (isRtl && event.key === 'ArrowLeft')) {\n        //\n        // When focus is on a closed node, opens the node; focus does not move.\n        // When focus is on a open node, moves focus to the first child node.\n        // When focus is on an end node (a tree item with no children), does nothing.\n        //\n        if (!activeItem || activeItem.disabled || activeItem.expanded || (activeItem.isLeaf && !activeItem.lazy)) {\n          focusItemAt(activeItemIndex + 1);\n        } else {\n          toggleExpand(true);\n        }\n      } else if ((isLtr && event.key === 'ArrowLeft') || (isRtl && event.key === 'ArrowRight')) {\n        //\n        // When focus is on an open node, closes the node.\n        // When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.\n        // When focus is on a closed `tree`, does nothing.\n        //\n        if (!activeItem || activeItem.disabled || activeItem.isLeaf || !activeItem.expanded) {\n          focusItemAt(activeItemIndex - 1);\n        } else {\n          toggleExpand(false);\n        }\n      } else if (event.key === 'Home') {\n        // Moves focus to the first node in the tree without opening or closing a node.\n        focusItemAt(0);\n      } else if (event.key === 'End') {\n        // Moves focus to the last node in the tree that is focusable without opening the node.\n        focusItemAt(items.length - 1);\n      } else if (event.key === 'Enter' || event.key === ' ') {\n        // Selects the focused node.\n        if (!activeItem.disabled) {\n          this.selectItem(activeItem);\n        }\n      }\n    }\n  }\n\n  private handleClick(event: Event) {\n    const target = event.target as SlTreeItem;\n    const treeItem = target.closest('sl-tree-item')!;\n    const isExpandButton = event\n      .composedPath()\n      .some((el: HTMLElement) => el?.classList?.contains('tree-item__expand-button'));\n\n    //\n    // Don't Do anything if there's no tree item, if it's disabled, or if the click doesn't match the initial target\n    // from mousedown. The latter case prevents the user from starting a click on one item and ending it on another,\n    // causing the parent node to collapse.\n    //\n    // See https://github.com/shoelace-style/shoelace/issues/1082\n    //\n    if (!treeItem || treeItem.disabled || target !== this.clickTarget) {\n      return;\n    }\n\n    if (isExpandButton) {\n      treeItem.expanded = !treeItem.expanded;\n    } else {\n      this.selectItem(treeItem);\n    }\n  }\n\n  handleMouseDown(event: MouseEvent) {\n    // Record the click target so we know which item the click initially targeted\n    this.clickTarget = event.target as SlTreeItem;\n  }\n\n  private handleFocusOut = (event: FocusEvent) => {\n    const relatedTarget = event.relatedTarget as HTMLElement;\n\n    // If the element that got the focus is not in the tree\n    if (!relatedTarget || !this.contains(relatedTarget)) {\n      this.tabIndex = 0;\n    }\n  };\n\n  private handleFocusIn = (event: FocusEvent) => {\n    const target = event.target as SlTreeItem;\n\n    // If the tree has been focused, move the focus to the last focused item\n    if (event.target === this) {\n      this.focusItem(this.lastFocusedItem || this.getAllTreeItems()[0]);\n    }\n\n    // If the target is a tree item, update the tabindex\n    if (SlTreeItem.isTreeItem(target) && !target.disabled) {\n      if (this.lastFocusedItem) {\n        this.lastFocusedItem.tabIndex = -1;\n      }\n      this.lastFocusedItem = target;\n      this.tabIndex = -1;\n\n      target.tabIndex = 0;\n    }\n  };\n\n  private handleSlotChange() {\n    const items = this.getAllTreeItems();\n    items.forEach(this.initTreeItem);\n  }\n\n  @watch('selection')\n  async handleSelectionChange() {\n    const isSelectionMultiple = this.selection === 'multiple';\n    const items = this.getAllTreeItems();\n\n    this.setAttribute('aria-multiselectable', isSelectionMultiple ? 'true' : 'false');\n\n    for (const item of items) {\n      item.selectable = isSelectionMultiple;\n    }\n\n    if (isSelectionMultiple) {\n      await this.updateComplete;\n\n      [...this.querySelectorAll(':scope > sl-tree-item')].forEach((treeItem: SlTreeItem) =>\n        syncCheckboxes(treeItem, true)\n      );\n    }\n  }\n\n  /** @internal Returns the list of tree items that are selected in the tree. */\n  get selectedItems(): SlTreeItem[] {\n    const items = this.getAllTreeItems();\n    const isSelected = (item: SlTreeItem) => item.selected;\n\n    return items.filter(isSelected);\n  }\n\n  /** @internal Gets focusable tree items in the tree. */\n  getFocusableItems() {\n    const items = this.getAllTreeItems();\n    const collapsedItems = new Set();\n\n    return items.filter(item => {\n      // Exclude disabled elements\n      if (item.disabled) return false;\n\n      // Exclude those whose parent is collapsed or loading\n      const parent: SlTreeItem | null | undefined = item.parentElement?.closest('[role=treeitem]');\n      if (parent && (!parent.expanded || parent.loading || collapsedItems.has(parent))) {\n        collapsedItems.add(item);\n      }\n\n      return !collapsedItems.has(item);\n    });\n  }\n\n  render() {\n    return html`\n      <div\n        part=\"base\"\n        class=\"tree\"\n        @click=${this.handleClick}\n        @keydown=${this.handleKeyDown}\n        @mousedown=${this.handleMouseDown}\n      >\n        <slot @slotchange=${this.handleSlotChange}></slot>\n        <span hidden aria-hidden=\"true\"><slot name=\"expand-icon\"></slot></span>\n        <span hidden aria-hidden=\"true\"><slot name=\"collapse-icon\"></slot></span>\n      </div>\n    `;\n  }\n}\n"
  },
  {
    "path": "src/components/tree/tree.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    /*\n     * These are actually used by tree item, but we define them here so they can more easily be set and all tree items\n     * stay consistent.\n     */\n    --indent-guide-color: var(--sl-color-neutral-200);\n    --indent-guide-offset: 0;\n    --indent-guide-style: solid;\n    --indent-guide-width: 0;\n    --indent-size: var(--sl-spacing-large);\n\n    display: block;\n\n    /*\n     * Tree item indentation uses the \"em\" unit to increment its width on each level, so setting the font size to zero\n     * here removes the indentation for all the nodes on the first level.\n     */\n    font-size: 0;\n  }\n`;\n"
  },
  {
    "path": "src/components/tree/tree.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { aTimeout, expect, fixture, html, triggerBlurFor, triggerFocusFor } from '@open-wc/testing';\nimport { clickOnElement } from '../../internal/test.js';\nimport { sendKeys } from '@web/test-runner-commands';\nimport sinon from 'sinon';\nimport type SlTree from './tree.component.js';\nimport type SlTreeItem from '../tree-item/tree-item.js';\n\ndescribe('<sl-tree>', () => {\n  let el: SlTree;\n\n  beforeEach(async () => {\n    el = await fixture(html`\n      <sl-tree>\n        <sl-tree-item>Node 1</sl-tree-item>\n        <sl-tree-item>Node 2</sl-tree-item>\n        <sl-tree-item id=\"expandable\">\n          Parent Node\n          <sl-tree-item>Child Node 1</sl-tree-item>\n          <sl-tree-item>\n            Child Node 2\n            <sl-tree-item>Child Node 2 - 1</sl-tree-item>\n            <sl-tree-item>Child Node 2 - 2</sl-tree-item>\n          </sl-tree-item>\n        </sl-tree-item>\n        <sl-tree-item>Node 3</sl-tree-item>\n      </sl-tree>\n    `);\n  });\n\n  it('should render a component', () => {\n    expect(el).to.exist;\n    expect(el).to.have.attribute('role', 'tree');\n    expect(el).to.have.attribute('tabindex', '0');\n  });\n\n  it('should pass accessibility tests', async () => {\n    await expect(el).to.be.accessible();\n  });\n\n  it('should not focus collapsed nodes', async () => {\n    // Arrange\n    const parentNode = el.children[2] as SlTreeItem;\n    const childNode = parentNode.children[1] as SlTreeItem;\n    childNode.expanded = true;\n    parentNode.expanded = false;\n\n    await el.updateComplete;\n\n    // Act\n    const focusableItems = el.getFocusableItems();\n\n    // Assert\n    expect(focusableItems).to.have.lengthOf(4);\n    expect(focusableItems).not.to.include.all.members([childNode, ...childNode.children]);\n    expect(focusableItems).not.to.include.all.members([...parentNode.children]);\n  });\n\n  describe('when a custom expanded/collapsed icon is provided', () => {\n    beforeEach(async () => {\n      el = await fixture(html`\n        <sl-tree>\n          <div slot=\"expand-icon\"></div>\n          <div slot=\"collapse-icon\"></div>\n\n          <sl-tree-item>Node 1</sl-tree-item>\n          <sl-tree-item>Node 2</sl-tree-item>\n        </sl-tree>\n      `);\n    });\n\n    it('should append a clone of the icon in the proper slot of the tree item', async () => {\n      // Arrange\n      await el.updateComplete;\n\n      // Act\n      const treeItems = [...el.querySelectorAll('sl-tree-item')];\n\n      // Assert\n      treeItems.forEach(treeItem => {\n        expect(treeItem.querySelector('div[slot=\"expand-icon\"]')).to.be.ok;\n        expect(treeItem.querySelector('div[slot=\"collapse-icon\"]')).to.be.ok;\n      });\n    });\n  });\n\n  describe('Keyboard navigation', () => {\n    describe('when ArrowDown is pressed', () => {\n      it('should move the focus to the next tree item', async () => {\n        // Arrange\n        el.focus();\n        await el.updateComplete;\n\n        // Act\n        await sendKeys({ press: 'ArrowDown' });\n\n        // Assert\n        expect(el).to.have.attribute('tabindex', '-1');\n        expect(el.children[0]).to.have.attribute('tabindex', '-1');\n        expect(el.children[1]).to.have.attribute('tabindex', '0');\n      });\n    });\n\n    describe('when ArrowUp is pressed', () => {\n      it('should move the focus to the prev tree item', async () => {\n        // Arrange\n        (el.children[1] as HTMLElement).focus();\n        await el.updateComplete;\n\n        // Act\n        await sendKeys({ press: 'ArrowUp' });\n\n        // Assert\n        expect(el).to.have.attribute('tabindex', '-1');\n        expect(el.children[0]).to.have.attribute('tabindex', '0');\n        expect(el.children[1]).to.have.attribute('tabindex', '-1');\n      });\n    });\n\n    describe('when ArrowRight is pressed', () => {\n      describe('and node is a leaf', () => {\n        it('should move the focus to the next tree item', async () => {\n          // Arrange\n          (el.children[0] as HTMLElement).focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowRight' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(el.children[0]).to.have.attribute('tabindex', '-1');\n          expect(el.children[1]).to.have.attribute('tabindex', '0');\n        });\n      });\n\n      describe('and node is collapsed', () => {\n        it('should expand the tree item', async () => {\n          // Arrange\n          const parentNode = el.children[2] as SlTreeItem;\n          parentNode.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowRight' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(parentNode).to.have.attribute('tabindex', '0');\n          expect(parentNode).to.have.attribute('expanded');\n        });\n      });\n\n      describe('and node is expanded', () => {\n        it('should move the focus to the next tree item', async () => {\n          // Arrange\n          const parentNode = el.children[2] as SlTreeItem;\n          parentNode.expanded = true;\n          parentNode.focus();\n\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowRight' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(parentNode).to.have.attribute('tabindex', '-1');\n          expect(parentNode.children[0]).to.have.attribute('tabindex', '0');\n        });\n      });\n    });\n\n    describe('when ArrowLeft is pressed', () => {\n      describe('and node is a leaf', () => {\n        it('should move the focus to the prev tree item', async () => {\n          // Arrange\n          (el.children[1] as HTMLElement).focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowLeft' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(el.children[0]).to.have.attribute('tabindex', '0');\n          expect(el.children[1]).to.have.attribute('tabindex', '-1');\n        });\n      });\n\n      describe('and node is collapsed', () => {\n        it('should move the focus to the prev tree item', async () => {\n          // Arrange\n          (el.children[2] as HTMLElement).focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowLeft' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(el.children[1]).to.have.attribute('tabindex', '0');\n          expect(el.children[2]).to.have.attribute('tabindex', '-1');\n        });\n      });\n\n      describe('and node is expanded', () => {\n        it('should collapse the tree item', async () => {\n          // Arrange\n          const parentNode = el.children[2] as SlTreeItem;\n          parentNode.expanded = true;\n          parentNode.focus();\n\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'ArrowLeft' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(parentNode).to.have.attribute('tabindex', '0');\n          expect(parentNode).not.to.have.attribute('expanded');\n        });\n      });\n    });\n\n    describe('when Home is pressed', () => {\n      it('should move the focus to the first tree item in the tree', async () => {\n        // Arrange\n        const parentNode = el.children[3] as SlTreeItem;\n        parentNode.focus();\n        await el.updateComplete;\n\n        // Act\n        await sendKeys({ press: 'Home' });\n\n        // Assert\n        expect(el).to.have.attribute('tabindex', '-1');\n        expect(el.children[0]).to.have.attribute('tabindex', '0');\n        expect(el.children[3]).to.have.attribute('tabindex', '-1');\n      });\n    });\n\n    describe('when End is pressed', () => {\n      it('should move the focus to the last tree item in the tree', async () => {\n        // Arrange\n        const parentNode = el.children[0] as SlTreeItem;\n        parentNode.focus();\n        await el.updateComplete;\n\n        // Act\n        await sendKeys({ press: 'End' });\n\n        // Assert\n        expect(el).to.have.attribute('tabindex', '-1');\n        expect(el.children[0]).to.have.attribute('tabindex', '-1');\n        expect(el.children[3]).to.have.attribute('tabindex', '0');\n      });\n    });\n\n    describe('when Enter is pressed', () => {\n      describe('and selection is \"single\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'single';\n          const node = el.children[1] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'Enter' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: 'Enter' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n          expect(el.children[2]).to.have.attribute('selected');\n        });\n      });\n\n      describe('and selection is \"leaf\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const node = el.children[0] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'Enter' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: 'Enter' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n        });\n\n        it('should expand/collapse a parent node', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const parentNode = el.children[2] as SlTreeItem;\n          parentNode.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'Enter' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(el.selectedItems.length).to.eq(0);\n          expect(parentNode).to.have.attribute('expanded');\n        });\n      });\n\n      describe('and selection is \"multiple\"', () => {\n        it('should toggle the selection on the tree item', async () => {\n          // Arrange\n          el.selection = 'multiple';\n          const node = el.children[1] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: 'Enter' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: 'Enter' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(6);\n        });\n      });\n    });\n\n    describe('when Space is pressed', () => {\n      describe('and selection is \"single\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'single';\n          const node = el.children[1] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: ' ' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: ' ' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n        });\n      });\n\n      describe('and selection is \"leaf\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const node = el.children[0] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: ' ' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: ' ' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n        });\n\n        it('should expand/collapse a parent node', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const parentNode = el.children[2] as SlTreeItem;\n          parentNode.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: ' ' });\n\n          // Assert\n          expect(el).to.have.attribute('tabindex', '-1');\n          expect(el.selectedItems.length).to.eq(0);\n          expect(parentNode).to.have.attribute('expanded');\n        });\n      });\n\n      describe('and selection is \"multiple\"', () => {\n        it('should toggle the selection on the tree item', async () => {\n          // Arrange\n          el.selection = 'multiple';\n          const node = el.children[0] as SlTreeItem;\n          node.focus();\n          await el.updateComplete;\n\n          // Act\n          await sendKeys({ press: ' ' });\n          await sendKeys({ press: 'ArrowRight' });\n          await sendKeys({ press: ' ' });\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(2);\n        });\n      });\n    });\n  });\n\n  describe('Interactions', () => {\n    describe('when the tree is about to receive the focus', () => {\n      it('should set the focus to the last focused item', async () => {\n        // Arrange\n        const node = el.children[1] as SlTreeItem;\n        node.focus();\n        await el.updateComplete;\n\n        // Act\n        triggerBlurFor(node);\n        triggerFocusFor(el);\n\n        // Assert\n        expect(el).to.have.attribute('tabindex', '-1');\n        expect(node).to.have.attribute('tabindex', '0');\n      });\n    });\n\n    describe('when the user clicks the expand button', () => {\n      it('should expand the tree item', async () => {\n        // Arrange\n        el.selection = 'single';\n        await el.updateComplete;\n\n        const node = el.children[2] as SlTreeItem;\n        await node.updateComplete;\n\n        const expandButton: HTMLElement = node.shadowRoot!.querySelector('.tree-item__expand-button')!;\n\n        // Act\n        await clickOnElement(expandButton);\n        await el.updateComplete;\n\n        // Assert\n        expect(node).to.have.attribute('expanded');\n      });\n    });\n\n    describe('when the user clicks on a tree item', () => {\n      describe('and selection is \"single\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'single';\n          const node0 = el.children[0] as SlTreeItem;\n          const node1 = el.children[1] as SlTreeItem;\n\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(node0);\n          await el.updateComplete;\n\n          await clickOnElement(node1);\n          await el.updateComplete;\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n        });\n      });\n\n      describe('and selection is \"leaf\"', () => {\n        it('should select only one tree item', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const node0 = el.children[0] as SlTreeItem;\n          const node1 = el.children[1] as SlTreeItem;\n\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(node0);\n          await el.updateComplete;\n\n          await clickOnElement(node1);\n          await el.updateComplete;\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(1);\n        });\n\n        it('should expand/collapse a parent node', async () => {\n          // Arrange\n          el.selection = 'leaf';\n          const parentNode = el.children[2] as SlTreeItem;\n\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(parentNode);\n          await parentNode.updateComplete;\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(0);\n          expect(parentNode).to.have.attribute('expanded');\n        });\n      });\n\n      describe('and selection is \"multiple\"', () => {\n        it('should toggle the selection on the tree item', async () => {\n          // Arrange\n          el.selection = 'multiple';\n          const node0 = el.children[0] as SlTreeItem;\n          const node1 = el.children[1] as SlTreeItem;\n\n          await el.updateComplete;\n\n          // Act\n          await clickOnElement(node0);\n          await el.updateComplete;\n\n          await clickOnElement(node1);\n          await el.updateComplete;\n\n          // Assert\n          expect(el.selectedItems.length).to.eq(2);\n        });\n\n        it('should select all the child tree items', async () => {\n          // Arrange\n          el.selection = 'multiple';\n          await el.updateComplete;\n\n          const parentNode = el.children[2] as SlTreeItem;\n\n          // Act\n          await clickOnElement(parentNode);\n          await el.updateComplete;\n\n          // Assert\n          expect(parentNode).to.have.attribute('selected');\n          expect(parentNode.indeterminate).to.be.false;\n          parentNode.getChildrenItems().forEach(child => {\n            expect(child).to.have.attribute('selected');\n          });\n        });\n\n        it('should set the indeterminate state to tree items if a child is selected', async () => {\n          // Arrange\n          el.selection = 'multiple';\n          await el.updateComplete;\n\n          const parentNode = el.children[2] as SlTreeItem;\n          const childNode = parentNode.children[0] as SlTreeItem;\n\n          // Act\n          parentNode.expanded = true;\n          await parentNode.updateComplete;\n          await aTimeout(300);\n          await clickOnElement(childNode);\n          await el.updateComplete;\n\n          // Assert\n          expect(parentNode).not.to.have.attribute('selected');\n          expect(parentNode.indeterminate).to.be.true;\n        });\n      });\n    });\n\n    describe('when selection is \"single\"', () => {\n      describe('and user clicks on same item twice', () => {\n        it('should emit `sl-selection-change` event once', async () => {\n          // Arrange\n          el.selection = 'single';\n          await el.updateComplete;\n\n          const selectedChangeSpy = sinon.spy();\n          el.addEventListener('sl-selection-change', selectedChangeSpy);\n\n          const node = el.children[0] as SlTreeItem;\n\n          // Act\n          await clickOnElement(node);\n          await el.updateComplete;\n          await clickOnElement(node);\n          await Promise.all([node.updateComplete, el.updateComplete]);\n\n          // Assert\n          expect(selectedChangeSpy).to.have.been.calledOnce;\n          expect(selectedChangeSpy.args[0][0]).to.deep.include({ detail: { selection: [node] } });\n        });\n      });\n    });\n  });\n\n  describe('when selection is \"leaf\"', () => {\n    describe('and user clicks on same leaf item twice', () => {\n      it('should emit `sl-selection-change` event once', async () => {\n        // Arrange\n        el.selection = 'leaf';\n        await el.updateComplete;\n\n        const selectedChangeSpy = sinon.spy();\n        el.addEventListener('sl-selection-change', selectedChangeSpy);\n\n        const node = el.children[0] as SlTreeItem;\n\n        // Act\n        await clickOnElement(node);\n        await el.updateComplete;\n        await clickOnElement(node);\n        await Promise.all([node.updateComplete, el.updateComplete]);\n\n        // Assert\n        expect(selectedChangeSpy).to.have.been.calledOnce;\n        expect(selectedChangeSpy.args[0][0]).to.deep.include({ detail: { selection: [node] } });\n      });\n    });\n\n    describe('and user clicks on expandable item', () => {\n      it('should not emit `sl-selection-change` event', async () => {\n        // Arrange\n        el.selection = 'leaf';\n        await el.updateComplete;\n\n        const selectedChangeSpy = sinon.spy();\n        el.addEventListener('sl-selection-change', selectedChangeSpy);\n\n        const node = el.querySelector<SlTreeItem>('#expandable')!;\n\n        // Act\n        await clickOnElement(node);\n        await Promise.all([node.updateComplete, el.updateComplete]);\n\n        // Assert\n        expect(selectedChangeSpy).to.not.have.been.called;\n      });\n    });\n  });\n\n  describe('when selection is \"multiple\"', () => {\n    describe('and user clicks on same item twice', () => {\n      it('should emit `sl-selection-change` event twice', async () => {\n        // Arrange\n        el.selection = 'multiple';\n        await el.updateComplete;\n\n        const selectedChangeSpy = sinon.spy();\n        el.addEventListener('sl-selection-change', selectedChangeSpy);\n\n        const node = el.children[0] as SlTreeItem;\n\n        // Act\n        await clickOnElement(node);\n        await Promise.all([node.updateComplete, el.updateComplete]);\n        await clickOnElement(node);\n        await Promise.all([node.updateComplete, el.updateComplete]);\n\n        // Assert\n        expect(selectedChangeSpy).to.have.been.calledTwice;\n        expect(selectedChangeSpy.args[0][0]).to.deep.include({ detail: { selection: [node] } });\n        expect(selectedChangeSpy.args[1][0]).to.deep.include({ detail: { selection: [] } });\n      });\n    });\n  });\n\n  describe('Checkboxes synchronization', () => {\n    describe('when the tree gets initialized', () => {\n      describe('and a parent node is selected', () => {\n        it('should select all the nested children', async () => {\n          // Arrange\n          const tree = await fixture<SlTree>(html`\n            <sl-tree selection=\"multiple\">\n              <sl-tree-item selected>\n                Parent Node\n                <sl-tree-item selected>Child Node 1</sl-tree-item>\n                <sl-tree-item>\n                  Child Node 2\n                  <sl-tree-item>Child Node 2 - 1</sl-tree-item>\n                  <sl-tree-item>Child Node 2 - 2</sl-tree-item>\n                </sl-tree-item>\n              </sl-tree-item>\n            </sl-tree>\n          `);\n          const treeItems = Array.from<SlTreeItem>(tree.querySelectorAll('sl-tree-item'));\n\n          // Act\n          await tree.updateComplete;\n\n          // Assert\n          treeItems.forEach(treeItem => {\n            expect(treeItem).to.have.attribute('selected');\n          });\n        });\n      });\n\n      describe('and a parent node is not selected', () => {\n        describe('and all the children are selected', () => {\n          it('should select the parent node', async () => {\n            // Arrange\n            const tree = await fixture<SlTree>(html`\n              <sl-tree selection=\"multiple\">\n                <sl-tree-item>\n                  Parent Node\n                  <sl-tree-item selected>Child Node 1</sl-tree-item>\n                  <sl-tree-item selected>\n                    Child Node 2\n                    <sl-tree-item>Child Node 2 - 1</sl-tree-item>\n                    <sl-tree-item>Child Node 2 - 2</sl-tree-item>\n                  </sl-tree-item>\n                </sl-tree-item>\n              </sl-tree>\n            `);\n            const treeItems = Array.from<SlTreeItem>(tree.querySelectorAll('sl-tree-item'));\n\n            // Act\n            await tree.updateComplete;\n\n            // Assert\n            treeItems.forEach(treeItem => {\n              expect(treeItem).to.have.attribute('selected');\n            });\n            expect(treeItems[0].indeterminate).to.be.false;\n          });\n        });\n\n        describe('and some of the children are selected', () => {\n          it('should set the parent node to indeterminate state', async () => {\n            // Arrange\n            const tree = await fixture<SlTree>(html`\n              <sl-tree selection=\"multiple\">\n                <sl-tree-item>\n                  Parent Node\n                  <sl-tree-item selected>Child Node 1</sl-tree-item>\n                  <sl-tree-item>\n                    Child Node 2\n                    <sl-tree-item>Child Node 2 - 1</sl-tree-item>\n                    <sl-tree-item>Child Node 2 - 2</sl-tree-item>\n                  </sl-tree-item>\n                </sl-tree-item>\n              </sl-tree>\n            `);\n            const treeItems = Array.from<SlTreeItem>(tree.querySelectorAll('sl-tree-item'));\n\n            // Act\n            await tree.updateComplete;\n\n            // Assert\n            expect(treeItems[0]).not.to.have.attribute('selected');\n            expect(treeItems[0].indeterminate).to.be.true;\n            expect(treeItems[1]).to.have.attribute('selected');\n            expect(treeItems[2]).not.to.have.attribute('selected');\n            expect(treeItems[3]).not.to.have.attribute('selected');\n            expect(treeItems[4]).not.to.have.attribute('selected');\n          });\n        });\n      });\n    });\n  });\n\n  // https://github.com/shoelace-style/shoelace/issues/1916\n  it(\"Should not render 'null' if it can't find a custom icon\", async () => {\n    const tree = await fixture<SlTree>(html`\n      <sl-tree>\n        <sl-tree-item>\n          Item 1\n          <sl-icon name=\"1-circle\" slot=\"expand-icon\"></sl-icon>\n          <sl-tree-item> Item A </sl-tree-item>\n        </sl-tree-item>\n        <sl-tree-item>\n          Item 2\n          <sl-tree-item>Item A</sl-tree-item>\n          <sl-tree-item>Item B</sl-tree-item>\n        </sl-tree-item>\n        <sl-tree-item>\n          Item 3\n          <sl-tree-item>Item A</sl-tree-item>\n          <sl-tree-item>Item B</sl-tree-item>\n        </sl-tree-item>\n      </sl-tree>\n    `);\n\n    expect(tree.textContent).to.not.includes('null');\n  });\n});\n"
  },
  {
    "path": "src/components/tree/tree.ts",
    "content": "import SlTree from './tree.component.js';\n\nexport * from './tree.component.js';\nexport default SlTree;\n\nSlTree.define('sl-tree');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tree': SlTree;\n  }\n}\n"
  },
  {
    "path": "src/components/tree-item/tree-item.component.ts",
    "content": "import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../internal/animate.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';\nimport { html } from 'lit';\nimport { live } from 'lit/directives/live.js';\nimport { LocalizeController } from '../../utilities/localize.js';\nimport { property, query, state } from 'lit/decorators.js';\nimport { watch } from '../../internal/watch.js';\nimport { when } from 'lit/directives/when.js';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport SlCheckbox from '../checkbox/checkbox.component.js';\nimport SlIcon from '../icon/icon.component.js';\nimport SlSpinner from '../spinner/spinner.component.js';\nimport styles from './tree-item.styles.js';\nimport type { CSSResultGroup, PropertyValueMap } from 'lit';\n\n/**\n * @summary A tree item serves as a hierarchical node that lives inside a [tree](/components/tree).\n * @documentation https://shoelace.style/components/tree-item\n * @status stable\n * @since 2.0\n *\n * @dependency sl-checkbox\n * @dependency sl-icon\n * @dependency sl-spinner\n *\n * @event sl-expand - Emitted when the tree item expands.\n * @event sl-after-expand - Emitted after the tree item expands and all animations are complete.\n * @event sl-collapse - Emitted when the tree item collapses.\n * @event sl-after-collapse - Emitted after the tree item collapses and all animations are complete.\n * @event sl-lazy-change - Emitted when the tree item's lazy state changes.\n * @event sl-lazy-load - Emitted when a lazy item is selected. Use this event to asynchronously load data and append\n *  items to the tree before expanding. After appending new items, remove the `lazy` attribute to remove the loading\n *  state and update the tree.\n *\n * @slot - The default slot.\n * @slot expand-icon - The icon to show when the tree item is expanded.\n * @slot collapse-icon - The icon to show when the tree item is collapsed.\n *\n * @csspart base - The component's base wrapper.\n * @csspart item - The tree item's container. This element wraps everything except slotted tree item children.\n * @csspart item--disabled - Applied when the tree item is disabled.\n * @csspart item--expanded - Applied when the tree item is expanded.\n * @csspart item--indeterminate - Applied when the selection is indeterminate.\n * @csspart item--selected - Applied when the tree item is selected.\n * @csspart indentation - The tree item's indentation container.\n * @csspart expand-button - The container that wraps the tree item's expand button and spinner.\n * @csspart spinner - The spinner that shows when a lazy tree item is in the loading state.\n * @csspart spinner__base - The spinner's base part.\n * @csspart label - The tree item's label.\n * @csspart children - The container that wraps the tree item's nested children.\n * @csspart checkbox - The checkbox that shows when using multiselect.\n * @csspart checkbox__base - The checkbox's exported `base` part.\n * @csspart checkbox__control - The checkbox's exported `control` part.\n * @csspart checkbox__control--checked - The checkbox's exported `control--checked` part.\n * @csspart checkbox__control--indeterminate - The checkbox's exported `control--indeterminate` part.\n * @csspart checkbox__checked-icon - The checkbox's exported `checked-icon` part.\n * @csspart checkbox__indeterminate-icon - The checkbox's exported `indeterminate-icon` part.\n * @csspart checkbox__label - The checkbox's exported `label` part.\n */\nexport default class SlTreeItem extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n  static dependencies = {\n    'sl-checkbox': SlCheckbox,\n    'sl-icon': SlIcon,\n    'sl-spinner': SlSpinner\n  };\n\n  static isTreeItem(node: Node) {\n    return node instanceof Element && node.getAttribute('role') === 'treeitem';\n  }\n\n  private readonly localize = new LocalizeController(this);\n\n  @state() indeterminate = false;\n  @state() isLeaf = false;\n  @state() loading = false;\n  @state() selectable = false;\n\n  /** Expands the tree item. */\n  @property({ type: Boolean, reflect: true }) expanded = false;\n\n  /** Draws the tree item in a selected state. */\n  @property({ type: Boolean, reflect: true }) selected = false;\n\n  /** Disables the tree item. */\n  @property({ type: Boolean, reflect: true }) disabled = false;\n\n  /** Enables lazy loading behavior. */\n  @property({ type: Boolean, reflect: true }) lazy = false;\n\n  @query('slot:not([name])') defaultSlot: HTMLSlotElement;\n  @query('slot[name=children]') childrenSlot: HTMLSlotElement;\n  @query('.tree-item__item') itemElement: HTMLDivElement;\n  @query('.tree-item__children') childrenContainer: HTMLDivElement;\n  @query('.tree-item__expand-button slot') expandButtonSlot: HTMLSlotElement;\n\n  connectedCallback() {\n    super.connectedCallback();\n\n    this.setAttribute('role', 'treeitem');\n    this.setAttribute('tabindex', '-1');\n\n    if (this.isNestedItem()) {\n      this.slot = 'children';\n    }\n  }\n\n  firstUpdated() {\n    this.childrenContainer.hidden = !this.expanded;\n    this.childrenContainer.style.height = this.expanded ? 'auto' : '0';\n\n    this.isLeaf = !this.lazy && this.getChildrenItems().length === 0;\n    this.handleExpandedChange();\n  }\n\n  private async animateCollapse() {\n    this.emit('sl-collapse');\n\n    await stopAnimations(this.childrenContainer);\n\n    const { keyframes, options } = getAnimation(this, 'tree-item.collapse', { dir: this.localize.dir() });\n    await animateTo(\n      this.childrenContainer,\n      shimKeyframesHeightAuto(keyframes, this.childrenContainer.scrollHeight),\n      options\n    );\n    this.childrenContainer.hidden = true;\n\n    this.emit('sl-after-collapse');\n  }\n\n  // Checks whether the item is nested into an item\n  private isNestedItem(): boolean {\n    const parent = this.parentElement;\n    return !!parent && SlTreeItem.isTreeItem(parent);\n  }\n\n  private handleChildrenSlotChange() {\n    this.loading = false;\n    this.isLeaf = !this.lazy && this.getChildrenItems().length === 0;\n  }\n\n  protected willUpdate(changedProperties: PropertyValueMap<SlTreeItem> | Map<PropertyKey, unknown>) {\n    if (changedProperties.has('selected') && !changedProperties.has('indeterminate')) {\n      this.indeterminate = false;\n    }\n  }\n\n  private async animateExpand() {\n    this.emit('sl-expand');\n\n    await stopAnimations(this.childrenContainer);\n    this.childrenContainer.hidden = false;\n\n    const { keyframes, options } = getAnimation(this, 'tree-item.expand', { dir: this.localize.dir() });\n    await animateTo(\n      this.childrenContainer,\n      shimKeyframesHeightAuto(keyframes, this.childrenContainer.scrollHeight),\n      options\n    );\n    this.childrenContainer.style.height = 'auto';\n\n    this.emit('sl-after-expand');\n  }\n\n  @watch('loading', { waitUntilFirstUpdate: true })\n  handleLoadingChange() {\n    this.setAttribute('aria-busy', this.loading ? 'true' : 'false');\n\n    if (!this.loading) {\n      this.animateExpand();\n    }\n  }\n\n  @watch('disabled')\n  handleDisabledChange() {\n    this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');\n  }\n\n  @watch('selected')\n  handleSelectedChange() {\n    this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n  }\n\n  @watch('expanded', { waitUntilFirstUpdate: true })\n  handleExpandedChange() {\n    if (!this.isLeaf) {\n      this.setAttribute('aria-expanded', this.expanded ? 'true' : 'false');\n    } else {\n      this.removeAttribute('aria-expanded');\n    }\n  }\n\n  @watch('expanded', { waitUntilFirstUpdate: true })\n  handleExpandAnimation() {\n    if (this.expanded) {\n      if (this.lazy) {\n        this.loading = true;\n\n        this.emit('sl-lazy-load');\n      } else {\n        this.animateExpand();\n      }\n    } else {\n      this.animateCollapse();\n    }\n  }\n\n  @watch('lazy', { waitUntilFirstUpdate: true })\n  handleLazyChange() {\n    this.emit('sl-lazy-change');\n  }\n\n  /** Gets all the nested tree items in this node. */\n  getChildrenItems({ includeDisabled = true }: { includeDisabled?: boolean } = {}): SlTreeItem[] {\n    return this.childrenSlot\n      ? ([...this.childrenSlot.assignedElements({ flatten: true })].filter(\n          (item: SlTreeItem) => SlTreeItem.isTreeItem(item) && (includeDisabled || !item.disabled)\n        ) as SlTreeItem[])\n      : [];\n  }\n\n  render() {\n    const isRtl = this.localize.dir() === 'rtl';\n    const showExpandButton = !this.loading && (!this.isLeaf || this.lazy);\n\n    return html`\n      <div\n        part=\"base\"\n        class=\"${classMap({\n          'tree-item': true,\n          'tree-item--expanded': this.expanded,\n          'tree-item--selected': this.selected,\n          'tree-item--disabled': this.disabled,\n          'tree-item--leaf': this.isLeaf,\n          'tree-item--has-expand-button': showExpandButton,\n          'tree-item--rtl': this.localize.dir() === 'rtl'\n        })}\"\n      >\n        <div\n          class=\"tree-item__item\"\n          part=\"\n            item\n            ${this.disabled ? 'item--disabled' : ''}\n            ${this.expanded ? 'item--expanded' : ''}\n            ${this.indeterminate ? 'item--indeterminate' : ''}\n            ${this.selected ? 'item--selected' : ''}\n          \"\n        >\n          <div class=\"tree-item__indentation\" part=\"indentation\"></div>\n\n          <div\n            part=\"expand-button\"\n            class=${classMap({\n              'tree-item__expand-button': true,\n              'tree-item__expand-button--visible': showExpandButton\n            })}\n            aria-hidden=\"true\"\n          >\n            ${when(\n              this.loading,\n              () => html` <sl-spinner part=\"spinner\" exportparts=\"base:spinner__base\"></sl-spinner> `\n            )}\n            <slot class=\"tree-item__expand-icon-slot\" name=\"expand-icon\">\n              <sl-icon library=\"system\" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>\n            </slot>\n            <slot class=\"tree-item__expand-icon-slot\" name=\"collapse-icon\">\n              <sl-icon library=\"system\" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>\n            </slot>\n          </div>\n\n          ${when(\n            this.selectable,\n            () => html`\n              <sl-checkbox\n                part=\"checkbox\"\n                exportparts=\"\n                    base:checkbox__base,\n                    control:checkbox__control,\n                    control--checked:checkbox__control--checked,\n                    control--indeterminate:checkbox__control--indeterminate,\n                    checked-icon:checkbox__checked-icon,\n                    indeterminate-icon:checkbox__indeterminate-icon,\n                    label:checkbox__label\n                  \"\n                class=\"tree-item__checkbox\"\n                ?disabled=\"${this.disabled}\"\n                ?checked=\"${live(this.selected)}\"\n                ?indeterminate=\"${this.indeterminate}\"\n                tabindex=\"-1\"\n              ></sl-checkbox>\n            `\n          )}\n\n          <slot class=\"tree-item__label\" part=\"label\"></slot>\n        </div>\n\n        <div class=\"tree-item__children\" part=\"children\" role=\"group\">\n          <slot name=\"children\" @slotchange=\"${this.handleChildrenSlotChange}\"></slot>\n        </div>\n      </div>\n    `;\n  }\n}\n\nsetDefaultAnimation('tree-item.expand', {\n  keyframes: [\n    { height: '0', opacity: '0', overflow: 'hidden' },\n    { height: 'auto', opacity: '1', overflow: 'hidden' }\n  ],\n  options: { duration: 250, easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)' }\n});\n\nsetDefaultAnimation('tree-item.collapse', {\n  keyframes: [\n    { height: 'auto', opacity: '1', overflow: 'hidden' },\n    { height: '0', opacity: '0', overflow: 'hidden' }\n  ],\n  options: { duration: 200, easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)' }\n});\n"
  },
  {
    "path": "src/components/tree-item/tree-item.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    display: block;\n    outline: 0;\n    z-index: 0;\n  }\n\n  :host(:focus) {\n    outline: none;\n  }\n\n  slot:not([name])::slotted(sl-icon) {\n    margin-inline-end: var(--sl-spacing-x-small);\n  }\n\n  .tree-item {\n    position: relative;\n    display: flex;\n    align-items: stretch;\n    flex-direction: column;\n    color: var(--sl-color-neutral-700);\n    cursor: pointer;\n    user-select: none;\n    -webkit-user-select: none;\n  }\n\n  .tree-item__checkbox {\n    pointer-events: none;\n  }\n\n  .tree-item__expand-button,\n  .tree-item__checkbox,\n  .tree-item__label {\n    font-family: var(--sl-font-sans);\n    font-size: var(--sl-font-size-medium);\n    font-weight: var(--sl-font-weight-normal);\n    line-height: var(--sl-line-height-dense);\n    letter-spacing: var(--sl-letter-spacing-normal);\n  }\n\n  .tree-item__checkbox::part(base) {\n    display: flex;\n    align-items: center;\n  }\n\n  .tree-item__indentation {\n    display: block;\n    width: 1em;\n    flex-shrink: 0;\n  }\n\n  .tree-item__expand-button {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    box-sizing: content-box;\n    color: var(--sl-color-neutral-500);\n    padding: var(--sl-spacing-x-small);\n    width: 1rem;\n    height: 1rem;\n    flex-shrink: 0;\n    cursor: pointer;\n  }\n\n  .tree-item__expand-button {\n    transition: var(--sl-transition-medium) rotate ease;\n  }\n\n  .tree-item--expanded .tree-item__expand-button {\n    rotate: 90deg;\n  }\n\n  .tree-item--expanded.tree-item--rtl .tree-item__expand-button {\n    rotate: -90deg;\n  }\n\n  .tree-item--expanded slot[name='expand-icon'],\n  .tree-item:not(.tree-item--expanded) slot[name='collapse-icon'] {\n    display: none;\n  }\n\n  .tree-item:not(.tree-item--has-expand-button) .tree-item__expand-icon-slot {\n    display: none;\n  }\n\n  .tree-item__expand-button--visible {\n    cursor: pointer;\n  }\n\n  .tree-item__item {\n    display: flex;\n    align-items: center;\n    border-inline-start: solid 3px transparent;\n  }\n\n  .tree-item--disabled .tree-item__item {\n    opacity: 0.5;\n    outline: none;\n    cursor: not-allowed;\n  }\n\n  :host(:focus-visible) .tree-item__item {\n    outline: var(--sl-focus-ring);\n    outline-offset: var(--sl-focus-ring-offset);\n    z-index: 2;\n  }\n\n  :host(:not([aria-disabled='true'])) .tree-item--selected .tree-item__item {\n    background-color: var(--sl-color-neutral-100);\n    border-inline-start-color: var(--sl-color-primary-600);\n  }\n\n  :host(:not([aria-disabled='true'])) .tree-item__expand-button {\n    color: var(--sl-color-neutral-600);\n  }\n\n  .tree-item__label {\n    display: flex;\n    align-items: center;\n    transition: var(--sl-transition-fast) color;\n  }\n\n  .tree-item__children {\n    display: block;\n    font-size: calc(1em + var(--indent-size, var(--sl-spacing-medium)));\n  }\n\n  /* Indentation lines */\n  .tree-item__children {\n    position: relative;\n  }\n\n  .tree-item__children::before {\n    content: '';\n    position: absolute;\n    top: var(--indent-guide-offset);\n    bottom: var(--indent-guide-offset);\n    left: calc(1em - (var(--indent-guide-width) / 2) - 1px);\n    border-inline-end: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n    z-index: 1;\n  }\n\n  .tree-item--rtl .tree-item__children::before {\n    left: auto;\n    right: 1em;\n  }\n\n  @media (forced-colors: active) {\n    :host(:not([aria-disabled='true'])) .tree-item--selected .tree-item__item {\n      outline: dashed 1px SelectedItem;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/components/tree-item/tree-item.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';\nimport sinon from 'sinon';\nimport type SlTreeItem from './tree-item.js';\n\ndescribe('<sl-tree-item>', () => {\n  let leafItem: SlTreeItem;\n  let parentItem: SlTreeItem;\n\n  beforeEach(async () => {\n    leafItem = await fixture(html` <sl-tree-item>Node 1</sl-tree-item> `);\n    parentItem = await fixture(html`\n      <sl-tree-item>\n        Parent Node\n        <sl-tree-item>Node 1</sl-tree-item>\n        <sl-tree-item>Node 1</sl-tree-item>\n      </sl-tree-item>\n    `);\n  });\n\n  it('should render a component', () => {\n    expect(leafItem).to.exist;\n    expect(parentItem).to.exist;\n\n    expect(leafItem).to.have.attribute('role', 'treeitem');\n    expect(leafItem).to.have.attribute('aria-selected', 'false');\n    expect(leafItem).to.have.attribute('aria-disabled', 'false');\n  });\n\n  describe('when it contains child tree items', () => {\n    it('should set isLeaf to false', () => {\n      // Assert\n      expect(parentItem.isLeaf).to.be.false;\n    });\n\n    it('should show the expand button', () => {\n      // Arrange\n      const expandButton = parentItem.shadowRoot?.querySelector('.tree-item__expand-button');\n\n      // Act\n\n      // Assert\n      expect(expandButton?.childElementCount).to.be.greaterThan(0);\n    });\n\n    it('should set the aria-expanded attribute', () => {\n      expect(parentItem).to.have.attribute('aria-expanded', 'false');\n    });\n  });\n\n  describe('when the user clicks the expand button', () => {\n    describe('and the item is collapsed', () => {\n      it('should emit sl-expand and sl-after-expand events', async () => {\n        // Arrange\n        const expandSpy = sinon.spy();\n        const afterExpandSpy = sinon.spy();\n\n        parentItem.addEventListener('sl-expand', expandSpy);\n        parentItem.addEventListener('sl-after-expand', afterExpandSpy);\n\n        // Act\n        parentItem.expanded = true;\n        await waitUntil(() => expandSpy.calledOnce);\n        await waitUntil(() => afterExpandSpy.calledOnce);\n\n        // Assert\n        expect(expandSpy).to.have.been.calledOnce;\n        expect(afterExpandSpy).to.have.been.calledOnce;\n      });\n    });\n\n    describe('and the item is expanded', () => {\n      it('should emit sl-collapse and sl-after-collapse events', async () => {\n        // Arrange\n        const collapseSpy = sinon.spy();\n        const afterCollapseSpy = sinon.spy();\n\n        parentItem.addEventListener('sl-collapse', collapseSpy);\n        parentItem.addEventListener('sl-after-collapse', afterCollapseSpy);\n\n        parentItem.expanded = true;\n        await oneEvent(parentItem, 'sl-after-expand');\n\n        // Act\n        parentItem.expanded = false;\n        await waitUntil(() => collapseSpy.calledOnce);\n        await waitUntil(() => afterCollapseSpy.calledOnce);\n\n        // Assert\n        expect(collapseSpy).to.have.been.calledOnce;\n        expect(afterCollapseSpy).to.have.been.calledOnce;\n      });\n\n      describe('and the item is disabled', () => {\n        it('should not expand', async () => {\n          // Arrange\n          const expandButton: HTMLElement = parentItem.shadowRoot!.querySelector('.tree-item__expand-button')!;\n          parentItem.disabled = true;\n\n          // Act\n          expandButton.click();\n          await parentItem.updateComplete;\n\n          // Assert\n          expect(parentItem).not.to.have.attribute('expanded');\n          expect(parentItem).to.have.attribute('aria-expanded', 'false');\n        });\n      });\n    });\n  });\n\n  describe('when the item is selected', () => {\n    it('should update the aria-selected attribute', async () => {\n      // Act\n      leafItem.selected = true;\n      await leafItem.updateComplete;\n\n      // Assert\n      expect(leafItem).to.have.attribute('aria-selected', 'true');\n    });\n\n    it('should set item--selected part', async () => {\n      // Act\n      leafItem.selected = true;\n      await leafItem.updateComplete;\n\n      // Assert\n      expect(leafItem.shadowRoot?.querySelector('.tree-item__item')?.part.contains('item--selected')).to.be.true;\n    });\n  });\n\n  describe('when the item is disabled', () => {\n    it('should update the aria-disabled attribute', async () => {\n      // Act\n      leafItem.disabled = true;\n      await leafItem.updateComplete;\n\n      // Assert\n      expect(leafItem).to.have.attribute('aria-disabled', 'true');\n    });\n\n    it('should set item--disabled part', async () => {\n      // Act\n      leafItem.disabled = true;\n      await leafItem.updateComplete;\n\n      // Assert\n      expect(leafItem.shadowRoot?.querySelector('.tree-item__item')?.part.contains('item--disabled')).to.be.true;\n    });\n  });\n\n  describe('when the item is expanded', () => {\n    it('should set item--expanded part', async () => {\n      // Act\n      leafItem.expanded = true;\n      await leafItem.updateComplete;\n\n      // Assert\n      expect(leafItem.shadowRoot?.querySelector('.tree-item__item')?.part.contains('item--expanded')).to.be.true;\n    });\n  });\n\n  describe('when the item is lazy', () => {\n    it('should emit sl-lazy-change when the lazy attribute is added and removed', async () => {\n      // Arrange\n      const lazyChangeSpy = sinon.spy();\n\n      parentItem.addEventListener('sl-lazy-change', lazyChangeSpy);\n      parentItem.lazy = true;\n\n      // Act\n      await waitUntil(() => lazyChangeSpy.calledOnce);\n      parentItem.lazy = false;\n      await waitUntil(() => lazyChangeSpy.calledOnce);\n\n      // Assert\n      expect(lazyChangeSpy).to.have.been.calledTwice;\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/tree-item/tree-item.ts",
    "content": "import SlTreeItem from './tree-item.component.js';\n\nexport * from './tree-item.component.js';\nexport default SlTreeItem;\n\nSlTreeItem.define('sl-tree-item');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-tree-item': SlTreeItem;\n  }\n}\n"
  },
  {
    "path": "src/components/visually-hidden/visually-hidden.component.ts",
    "content": "import { html } from 'lit';\nimport componentStyles from '../../styles/component.styles.js';\nimport ShoelaceElement from '../../internal/shoelace-element.js';\nimport styles from './visually-hidden.styles.js';\nimport type { CSSResultGroup } from 'lit';\n\n/**\n * @summary The visually hidden utility makes content accessible to assistive devices without displaying it on the screen.\n * @documentation https://shoelace.style/components/visually-hidden\n * @status stable\n * @since 2.0\n *\n * @slot - The content to be visually hidden.\n */\nexport default class SlVisuallyHidden extends ShoelaceElement {\n  static styles: CSSResultGroup = [componentStyles, styles];\n\n  render() {\n    return html` <slot></slot> `;\n  }\n}\n"
  },
  {
    "path": "src/components/visually-hidden/visually-hidden.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host(:not(:focus-within)) {\n    position: absolute !important;\n    width: 1px !important;\n    height: 1px !important;\n    clip: rect(0 0 0 0) !important;\n    clip-path: inset(50%) !important;\n    border: none !important;\n    overflow: hidden !important;\n    white-space: nowrap !important;\n    padding: 0 !important;\n  }\n`;\n"
  },
  {
    "path": "src/components/visually-hidden/visually-hidden.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport { expect, fixture, html } from '@open-wc/testing';\n\ndescribe('<sl-visually-hidden>', () => {\n  it('should render but not display visually hidden content', async () => {\n    const el = await fixture(html`\n      <sl-visually-hidden>\n        <a href=\"#\">Skip to main content</a>\n      </sl-visually-hidden>\n    `);\n\n    const { width, height, overflow, clipPath } = getComputedStyle(el);\n\n    expect(width).to.equal('1px');\n    expect(height).to.equal('1px');\n    expect(overflow).to.equal('hidden');\n    expect(clipPath).to.equal('inset(50%)');\n  });\n\n  // should show visually hidden content when focused\n  it('should show visually hidden content when focused', async () => {\n    const el = await fixture(html`\n      <sl-visually-hidden>\n        <a href=\"#\">Skip to main content</a>\n      </sl-visually-hidden>\n    `);\n\n    const a = el.querySelector('a')!;\n    a.focus();\n\n    const { width, height, overflow, clipPath } = getComputedStyle(el);\n\n    expect(width).not.to.equal('1px');\n    expect(height).not.to.equal('1px');\n    expect(overflow).not.to.equal('hidden');\n    expect(clipPath).not.to.equal('inset(50%)');\n  });\n});\n"
  },
  {
    "path": "src/components/visually-hidden/visually-hidden.ts",
    "content": "import SlVisuallyHidden from './visually-hidden.component.js';\n\nexport * from './visually-hidden.component.js';\nexport default SlVisuallyHidden;\n\nSlVisuallyHidden.define('sl-visually-hidden');\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'sl-visually-hidden': SlVisuallyHidden;\n  }\n}\n"
  },
  {
    "path": "src/declaration.d.ts",
    "content": "declare module '*.css' {\n  const styles: string;\n  export default styles;\n}\n\ndeclare namespace Chai {\n  interface Assertion {\n    // chai-a11y-axe returns a promise-like object and should be awaited but the types are incorrect\n    // eslint-disable-next-line @typescript-eslint/ban-types\n    accessible: (options?: Object) => PromiseLike<Assertion>;\n  }\n}\n\ninterface HTMLInputElement {\n  showPicker: () => void;\n}\n\n/* eslint-disable */\ninterface CloseWatcher extends EventTarget {\n  new (options?: CloseWatcherOptions): CloseWatcher;\n  requestClose(): void;\n  close(): void;\n  destroy(): void;\n\n  oncancel: (event: Event) => void | null;\n  onclose: (event: Event) => void | null;\n}\n\ndeclare const CloseWatcher: CloseWatcher;\n\ninterface CloseWatcherOptions {\n  signal: AbortSignal;\n}\n\ndeclare interface Window {\n  CloseWatcher?: CloseWatcher;\n}\n/* eslint-enable */\n"
  },
  {
    "path": "src/events/events.ts",
    "content": "export type { SlAfterCollapseEvent } from './sl-after-collapse.js';\nexport type { SlAfterExpandEvent } from './sl-after-expand.js';\nexport type { SlAfterHideEvent } from './sl-after-hide.js';\nexport type { SlAfterShowEvent } from './sl-after-show.js';\nexport type { SlBlurEvent } from './sl-blur.js';\nexport type { SlCancelEvent } from './sl-cancel.js';\nexport type { SlChangeEvent } from './sl-change.js';\nexport type { SlClearEvent } from './sl-clear.js';\nexport type { SlCloseEvent } from './sl-close.js';\nexport type { SlCollapseEvent } from './sl-collapse.js';\nexport type { SlCopyEvent } from './sl-copy.js';\nexport type { SlErrorEvent } from './sl-error.js';\nexport type { SlExpandEvent } from './sl-expand.js';\nexport type { SlFinishEvent } from './sl-finish.js';\nexport type { SlFocusEvent } from './sl-focus.js';\nexport type { SlHideEvent } from './sl-hide.js';\nexport type { SlHoverEvent } from './sl-hover.js';\nexport type { SlInitialFocusEvent } from './sl-initial-focus.js';\nexport type { SlInputEvent } from './sl-input.js';\nexport type { SlInvalidEvent } from './sl-invalid.js';\nexport type { SlLazyChangeEvent } from './sl-lazy-change.js';\nexport type { SlLazyLoadEvent } from './sl-lazy-load.js';\nexport type { SlLoadEvent } from './sl-load.js';\nexport type { SlMutationEvent } from './sl-mutation.js';\nexport type { SlRemoveEvent } from './sl-remove.js';\nexport type { SlRepositionEvent } from './sl-reposition.js';\nexport type { SlRequestCloseEvent } from './sl-request-close.js';\nexport type { SlResizeEvent } from './sl-resize.js';\nexport type { SlSelectEvent } from './sl-select.js';\nexport type { SlSelectionChangeEvent } from './sl-selection-change.js';\nexport type { SlShowEvent } from './sl-show.js';\nexport type { SlSlideChangeEvent } from './sl-slide-change.js';\nexport type { SlStartEvent } from './sl-start.js';\nexport type { SlTabHideEvent } from './sl-tab-hide.js';\nexport type { SlTabShowEvent } from './sl-tab-show.js';\n"
  },
  {
    "path": "src/events/sl-after-collapse.ts",
    "content": "export type SlAfterCollapseEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-after-collapse': SlAfterCollapseEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-after-expand.ts",
    "content": "export type SlAfterExpandEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-after-expand': SlAfterExpandEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-after-hide.ts",
    "content": "export type SlAfterHideEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-after-hide': SlAfterHideEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-after-show.ts",
    "content": "export type SlAfterShowEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-after-show': SlAfterShowEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-blur.ts",
    "content": "export type SlBlurEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-blur': SlBlurEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-cancel.ts",
    "content": "export type SlCancelEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-cancel': SlCancelEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-change.ts",
    "content": "export type SlChangeEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-change': SlChangeEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-clear.ts",
    "content": "export type SlClearEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-clear': SlClearEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-close.ts",
    "content": "export type SlCloseEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-close': SlCloseEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-collapse.ts",
    "content": "export type SlCollapseEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-collapse': SlCollapseEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-copy.ts",
    "content": "export type SlCopyEvent = CustomEvent<{ value: string }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-copy': SlCopyEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-error.ts",
    "content": "export type SlErrorEvent = CustomEvent<{ status?: number }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-error': SlErrorEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-expand.ts",
    "content": "export type SlExpandEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-expand': SlExpandEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-finish.ts",
    "content": "export type SlFinishEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-finish': SlFinishEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-focus.ts",
    "content": "export type SlFocusEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-focus': SlFocusEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-hide.ts",
    "content": "export type SlHideEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-hide': SlHideEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-hover.ts",
    "content": "export type SlHoverEvent = CustomEvent<{\n  phase: 'start' | 'move' | 'end';\n  value: number;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-hover': SlHoverEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-initial-focus.ts",
    "content": "export type SlInitialFocusEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-initial-focus': SlInitialFocusEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-input.ts",
    "content": "export type SlInputEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-input': SlInputEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-invalid.ts",
    "content": "export type SlInvalidEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-invalid': SlInvalidEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-lazy-change.ts",
    "content": "export type SlLazyChangeEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-lazy-change': SlLazyChangeEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-lazy-load.ts",
    "content": "export type SlLazyLoadEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-lazy-load': SlLazyLoadEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-load.ts",
    "content": "export type SlLoadEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-load': SlLoadEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-mutation.ts",
    "content": "export type SlMutationEvent = CustomEvent<{ mutationList: MutationRecord[] }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-mutation': SlMutationEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-remove.ts",
    "content": "export type SlRemoveEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-remove': SlRemoveEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-reposition.ts",
    "content": "export type SlRepositionEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-reposition': SlRepositionEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-request-close.ts",
    "content": "export type SlRequestCloseEvent = CustomEvent<{ source: 'close-button' | 'keyboard' | 'overlay' }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-request-close': SlRequestCloseEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-resize.ts",
    "content": "export type SlResizeEvent = CustomEvent<{ entries: ResizeObserverEntry[] }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-resize': SlResizeEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-select.ts",
    "content": "import type SlMenuItem from '../components/menu-item/menu-item.js';\n\nexport type SlSelectEvent = CustomEvent<{ item: SlMenuItem }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-select': SlSelectEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-selection-change.ts",
    "content": "import type SlTreeItem from '../components/tree-item/tree-item.js';\n\nexport type SlSelectionChangeEvent = CustomEvent<{ selection: SlTreeItem[] }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-selection-change': SlSelectionChangeEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-show.ts",
    "content": "export type SlShowEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-show': SlShowEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-slide-change.ts",
    "content": "import type SlCarouselItem from '../components/carousel-item/carousel-item.js';\n\nexport type SlSlideChangeEvent = CustomEvent<{ index: number; slide: SlCarouselItem }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-slide-change': SlSlideChangeEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-start.ts",
    "content": "export type SlStartEvent = CustomEvent<Record<PropertyKey, never>>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-start': SlStartEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-tab-hide.ts",
    "content": "export type SlTabHideEvent = CustomEvent<{ name: string }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-tab-hide': SlTabHideEvent;\n  }\n}\n"
  },
  {
    "path": "src/events/sl-tab-show.ts",
    "content": "export type SlTabShowEvent = CustomEvent<{ name: string }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'sl-tab-show': SlTabShowEvent;\n  }\n}\n"
  },
  {
    "path": "src/internal/active-elements.ts",
    "content": "/**\n * Use a generator so we can iterate and possibly break early.\n * @example\n *   // to operate like a regular array. This kinda nullifies generator benefits, but worth knowing if you need the whole array.\n *   const allActiveElements = [...activeElements()]\n *\n *   // Early return\n *   for (const activeElement of activeElements()) {\n *     if (<cond>) {\n *       break; // Break the loop, dont need to iterate over the whole array or store an array in memory!\n *     }\n *   }\n */\nexport function* activeElements(activeElement: Element | null = document.activeElement): Generator<Element> {\n  if (activeElement === null || activeElement === undefined) return;\n\n  yield activeElement;\n\n  if ('shadowRoot' in activeElement && activeElement.shadowRoot && activeElement.shadowRoot.mode !== 'closed') {\n    yield* activeElements(activeElement.shadowRoot.activeElement);\n  }\n}\n\nexport function getDeepestActiveElement() {\n  return [...activeElements()].pop();\n}\n"
  },
  {
    "path": "src/internal/animate.ts",
    "content": "/**\n * Animates an element using keyframes. Returns a promise that resolves after the animation completes or gets canceled.\n */\nexport function animateTo(el: HTMLElement, keyframes: Keyframe[], options?: KeyframeAnimationOptions) {\n  return new Promise(resolve => {\n    if (options?.duration === Infinity) {\n      throw new Error('Promise-based animations must be finite.');\n    }\n\n    const animation = el.animate(keyframes, {\n      ...options,\n      duration: prefersReducedMotion() ? 0 : options!.duration\n    });\n\n    animation.addEventListener('cancel', resolve, { once: true });\n    animation.addEventListener('finish', resolve, { once: true });\n  });\n}\n\n/** Parses a CSS duration and returns the number of milliseconds. */\nexport function parseDuration(delay: number | string) {\n  delay = delay.toString().toLowerCase();\n\n  if (delay.indexOf('ms') > -1) {\n    return parseFloat(delay);\n  }\n\n  if (delay.indexOf('s') > -1) {\n    return parseFloat(delay) * 1000;\n  }\n\n  return parseFloat(delay);\n}\n\n/** Tells if the user has enabled the \"reduced motion\" setting in their browser or OS. */\nexport function prefersReducedMotion() {\n  const query = window.matchMedia('(prefers-reduced-motion: reduce)');\n  return query.matches;\n}\n\n/**\n * Stops all active animations on the target element. Returns a promise that resolves after all animations are canceled.\n */\nexport function stopAnimations(el: HTMLElement) {\n  return Promise.all(\n    el.getAnimations().map(animation => {\n      return new Promise(resolve => {\n        animation.cancel();\n        requestAnimationFrame(resolve);\n      });\n    })\n  );\n}\n\n/**\n * We can't animate `height: auto`, but we can calculate the height and shim keyframes by replacing it with the\n * element's scrollHeight before the animation.\n */\nexport function shimKeyframesHeightAuto(keyframes: Keyframe[], calculatedHeight: number) {\n  return keyframes.map(keyframe => ({\n    ...keyframe,\n    height: keyframe.height === 'auto' ? `${calculatedHeight}px` : keyframe.height\n  }));\n}\n"
  },
  {
    "path": "src/internal/closeActiveElement.ts",
    "content": "/**\n * Calls the blur method on the current active element if it is a child of the provided element.\n * Needed for fixing a11y errors in console.\n * @see https://github.com/shoelace-style/shoelace/issues/2283\n * @param elm The element to check\n */\nexport const blurActiveElement = (elm: HTMLElement) => {\n  const { activeElement } = document;\n  if (activeElement && elm.contains(activeElement)) {\n    (document.activeElement as HTMLElement)?.blur();\n  }\n};\n"
  },
  {
    "path": "src/internal/debounce.ts",
    "content": "// @debounce decorator\n//\n// Delays the execution until the provided delay in milliseconds has\n// passed since the last time the function has been called.\n//\n//\n// Usage:\n//\n//  @debounce(1000)\n//  handleInput() {\n//    ...\n//  }\n//\n\n// Each class instance will need to store its timer id, so this unique symbol will be used as property key.\nconst TIMER_ID_KEY = Symbol();\n\nexport const debounce = (delay: number) => {\n  return <T>(_target: T, _propertyKey: string, descriptor: PropertyDescriptor) => {\n    const fn = descriptor.value as (this: T & { [TIMER_ID_KEY]: number }, ...args: unknown[]) => unknown;\n\n    descriptor.value = function (this: ThisParameterType<typeof fn>, ...args: Parameters<typeof fn>) {\n      clearTimeout(this[TIMER_ID_KEY]);\n\n      this[TIMER_ID_KEY] = window.setTimeout(() => {\n        fn.apply(this, args);\n      }, delay);\n    };\n  };\n};\n"
  },
  {
    "path": "src/internal/default-value.ts",
    "content": "// @defaultValue decorator\n//\n// Runs when the corresponding attribute of the observed property changes, e.g. after calling Element.setAttribute or after updating\n// the observed property.\n//\n// The decorator checks whether the value of the attribute is different from the value of the property and in that case\n// it saves the new value.\n//\n//\n// Usage:\n//\n//  @property({ type: Boolean, reflect: true }) checked = false;\n//\n//  @defaultValue('checked') defaultChecked = false;\n//\n\nimport { defaultConverter } from 'lit';\nimport type { ReactiveElement } from 'lit';\n\nexport const defaultValue =\n  (propertyName = 'value') =>\n  (proto: ReactiveElement, key: string) => {\n    const ctor = proto.constructor as typeof ReactiveElement;\n\n    const attributeChangedCallback = ctor.prototype.attributeChangedCallback;\n    ctor.prototype.attributeChangedCallback = function (\n      this: ReactiveElement & { [name: string]: unknown },\n      name,\n      old,\n      value\n    ) {\n      const options = ctor.getPropertyOptions(propertyName);\n      const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyName;\n\n      if (name === attributeName) {\n        const converter = options.converter || defaultConverter;\n        const fromAttribute =\n          typeof converter === 'function' ? converter : (converter?.fromAttribute ?? defaultConverter.fromAttribute);\n\n        const newValue: unknown = fromAttribute!(value, options.type);\n\n        if (this[propertyName] !== newValue) {\n          this[key] = newValue;\n        }\n      }\n\n      attributeChangedCallback.call(this, name, old, value);\n    };\n  };\n"
  },
  {
    "path": "src/internal/drag.ts",
    "content": "interface DragOptions {\n  /** Callback that runs as dragging occurs. */\n  onMove: (x: number, y: number) => void;\n  /** Callback that runs when dragging stops. */\n  onStop: () => void;\n  /**\n   * When an initial event is passed, the first drag will be triggered immediately using the coordinates therein. This\n   * is useful when the drag is initiated by a mousedown/touchstart event but you want the initial \"click\" to activate\n   * a drag (e.g. positioning a handle initially at the click target).\n   */\n  initialEvent: PointerEvent;\n}\n\n/** Begins listening for dragging. */\nexport function drag(container: HTMLElement, options?: Partial<DragOptions>) {\n  function move(pointerEvent: PointerEvent) {\n    const dims = container.getBoundingClientRect();\n    const defaultView = container.ownerDocument.defaultView!;\n    const offsetX = dims.left + defaultView.scrollX;\n    const offsetY = dims.top + defaultView.scrollY;\n    const x = pointerEvent.pageX - offsetX;\n    const y = pointerEvent.pageY - offsetY;\n\n    if (options?.onMove) {\n      options.onMove(x, y);\n    }\n  }\n\n  function stop() {\n    document.removeEventListener('pointermove', move);\n    document.removeEventListener('pointerup', stop);\n\n    if (options?.onStop) {\n      options.onStop();\n    }\n  }\n\n  document.addEventListener('pointermove', move, { passive: true });\n  document.addEventListener('pointerup', stop);\n\n  // If an initial event is set, trigger the first drag immediately\n  if (options?.initialEvent instanceof PointerEvent) {\n    move(options.initialEvent);\n  }\n}\n"
  },
  {
    "path": "src/internal/event.ts",
    "content": "/** Waits for a specific event to be emitted from an element. Ignores events that bubble up from child elements. */\nexport function waitForEvent(el: HTMLElement, eventName: string) {\n  return new Promise<void>(resolve => {\n    function done(event: Event) {\n      if (event.target === el) {\n        el.removeEventListener(eventName, done);\n        resolve();\n      }\n    }\n\n    el.addEventListener(eventName, done);\n  });\n}\n"
  },
  {
    "path": "src/internal/form.test.ts",
    "content": "import '../../../dist/shoelace.js';\nimport sinon from 'sinon';\n\nimport { expect, fixture, html, waitUntil } from '@open-wc/testing';\n\n// Reproduction of this issue: https://github.com/shoelace-style/shoelace/issues/1703\nit('Should still run form validations if an element is removed', async () => {\n  const form = await fixture<HTMLFormElement>(html`\n    <form>\n      <sl-input name=\"name\" label=\"Name\" required></sl-input>\n      <sl-textarea name=\"comment\" label=\"Comment\" required></sl-textarea>\n    </form>\n  `);\n\n  expect(form.checkValidity()).to.equal(false);\n  expect(form.reportValidity()).to.equal(false);\n\n  form.querySelector('sl-input')!.remove();\n\n  expect(form.checkValidity()).to.equal(false);\n  expect(form.reportValidity()).to.equal(false);\n});\n\nit('should submit the correct form values', async () => {\n  const form = await fixture<HTMLFormElement>(html`\n    <form>\n      <sl-input name=\"a\" value=\"1\"></sl-input>\n      <sl-input name=\"b\" value=\"2\"></sl-input>\n      <sl-input name=\"c\" value=\"3\"></sl-input>\n      <sl-button type=\"submit\">Submit</sl-button>\n    </form>\n  `);\n\n  const button = form.querySelector('sl-button')!;\n  const submitHandler = sinon.spy((event: SubmitEvent) => {\n    formData = new FormData(form);\n    event.preventDefault();\n  });\n  let formData: FormData;\n\n  form.addEventListener('submit', submitHandler);\n  button.click();\n\n  await waitUntil(() => submitHandler.calledOnce);\n\n  expect(formData!.get('a')).to.equal('1');\n  expect(formData!.get('b')).to.equal('2');\n  expect(formData!.get('c')).to.equal('3');\n});\n\nit('should submit the correct form values when form controls are removed from the DOM', async () => {\n  const form = await fixture<HTMLFormElement>(html`\n    <form>\n      <sl-input name=\"a\" value=\"1\"></sl-input>\n      <sl-input name=\"b\" value=\"2\"></sl-input>\n      <sl-input name=\"c\" value=\"3\"></sl-input>\n      <sl-button type=\"submit\">Submit</sl-button>\n    </form>\n  `);\n\n  const button = form.querySelector('sl-button')!;\n  const submitHandler = sinon.spy((event: SubmitEvent) => {\n    formData = new FormData(form);\n    event.preventDefault();\n  });\n  let formData: FormData;\n\n  form.addEventListener('submit', submitHandler);\n  form.querySelector('[name=\"b\"]')!.remove();\n\n  button.click();\n\n  await waitUntil(() => submitHandler.calledOnce);\n\n  expect(formData!.get('a')).to.equal('1');\n  expect(formData!.get('b')).to.equal(null);\n  expect(formData!.get('c')).to.equal('3');\n});\n"
  },
  {
    "path": "src/internal/form.ts",
    "content": "import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport type { ShoelaceFormControl } from '../internal/shoelace-element.js';\nimport type SlButton from '../components/button/button.js';\n\n//\n// We store a WeakMap of forms + controls so we can keep references to all Shoelace controls within a given form. As\n// elements connect and disconnect to/from the DOM, their containing form is used as the key and the form control is\n// added and removed from the form's set, respectively.\n//\nexport const formCollections: WeakMap<HTMLFormElement, Set<ShoelaceFormControl>> = new WeakMap();\n\n//\n// We store a WeakMap of reportValidity() overloads so we can override it when form controls connect to the DOM and\n// restore the original behavior when they disconnect.\n//\nconst reportValidityOverloads: WeakMap<HTMLFormElement, () => boolean> = new WeakMap();\nconst checkValidityOverloads: WeakMap<HTMLFormElement, () => boolean> = new WeakMap();\n\n//\n// We store a Set of controls that users have interacted with. This allows us to determine the interaction state\n// without littering the DOM with additional data attributes.\n//\nconst userInteractedControls: WeakSet<ShoelaceFormControl> = new WeakSet();\n\n//\n// We store a WeakMap of interactions for each form control so we can track when all conditions are met for validation.\n//\nconst interactions = new WeakMap<ShoelaceFormControl, string[]>();\n\nexport interface FormControlControllerOptions {\n  /** A function that returns the form containing the form control. */\n  form: (input: ShoelaceFormControl) => HTMLFormElement | null;\n  /** A function that returns the form control's name, which will be submitted with the form data. */\n  name: (input: ShoelaceFormControl) => string;\n  /** A function that returns the form control's current value. */\n  value: (input: ShoelaceFormControl) => unknown | unknown[];\n  /** A function that returns the form control's default value. */\n  defaultValue: (input: ShoelaceFormControl) => unknown | unknown[];\n  /** A function that returns the form control's current disabled state. If disabled, the value won't be submitted. */\n  disabled: (input: ShoelaceFormControl) => boolean;\n  /**\n   * A function that maps to the form control's reportValidity() function. When the control is invalid, this will\n   * prevent submission and trigger the browser's constraint violation warning.\n   */\n  reportValidity: (input: ShoelaceFormControl) => boolean;\n\n  /**\n   * A function that maps to the form control's `checkValidity()` function. When the control is invalid, this will return false.\n   *   this is helpful is you want to check validation without triggering the native browser constraint violation warning.\n   */\n  checkValidity: (input: ShoelaceFormControl) => boolean;\n  /** A function that sets the form control's value */\n  setValue: (input: ShoelaceFormControl, value: unknown) => void;\n  /**\n   * An array of event names to listen to. When all events in the list are emitted, the control will receive validity\n   * states such as user-valid and user-invalid.user interacted validity states. */\n  assumeInteractionOn: string[];\n}\n\n/** A reactive controller to allow form controls to participate in form submission, validation, etc. */\nexport class FormControlController implements ReactiveController {\n  host: ShoelaceFormControl & ReactiveControllerHost;\n  form?: HTMLFormElement | null;\n  options: FormControlControllerOptions;\n\n  constructor(host: ReactiveControllerHost & ShoelaceFormControl, options?: Partial<FormControlControllerOptions>) {\n    (this.host = host).addController(this);\n    this.options = {\n      form: input => {\n        // If there's a form attribute, use it to find the target form by id\n        // Controls may not always reflect the 'form' property. For example, `<sl-button>` doesn't reflect.\n        const formId = input.form;\n\n        if (formId) {\n          const root = input.getRootNode() as Document | ShadowRoot | HTMLElement;\n          const form = root.querySelector(`#${formId}`);\n\n          if (form) {\n            return form as HTMLFormElement;\n          }\n        }\n\n        return input.closest('form');\n      },\n      name: input => input.name,\n      value: input => input.value,\n      defaultValue: input => input.defaultValue,\n      disabled: input => input.disabled ?? false,\n      reportValidity: input => (typeof input.reportValidity === 'function' ? input.reportValidity() : true),\n      checkValidity: input => (typeof input.checkValidity === 'function' ? input.checkValidity() : true),\n      setValue: (input, value: string) => (input.value = value),\n      assumeInteractionOn: ['sl-input'],\n      ...options\n    };\n  }\n\n  hostConnected() {\n    const form = this.options.form(this.host);\n\n    if (form) {\n      this.attachForm(form);\n    }\n\n    // Listen for interactions\n    interactions.set(this.host, []);\n    this.options.assumeInteractionOn.forEach(event => {\n      this.host.addEventListener(event, this.handleInteraction);\n    });\n  }\n\n  hostDisconnected() {\n    this.detachForm();\n\n    // Clean up interactions\n    interactions.delete(this.host);\n    this.options.assumeInteractionOn.forEach(event => {\n      this.host.removeEventListener(event, this.handleInteraction);\n    });\n  }\n\n  hostUpdated() {\n    const form = this.options.form(this.host);\n\n    // Detach if the form no longer exists\n    if (!form) {\n      this.detachForm();\n    }\n\n    // If the form has changed, reattach it\n    if (form && this.form !== form) {\n      this.detachForm();\n      this.attachForm(form);\n    }\n\n    if (this.host.hasUpdated) {\n      this.setValidity(this.host.validity.valid);\n    }\n  }\n\n  private attachForm(form?: HTMLFormElement) {\n    if (form) {\n      this.form = form;\n\n      // Add this element to the form's collection\n      if (formCollections.has(this.form)) {\n        formCollections.get(this.form)!.add(this.host);\n      } else {\n        formCollections.set(this.form, new Set<ShoelaceFormControl>([this.host]));\n      }\n\n      this.form.addEventListener('formdata', this.handleFormData);\n      this.form.addEventListener('submit', this.handleFormSubmit);\n      this.form.addEventListener('reset', this.handleFormReset);\n\n      // Overload the form's reportValidity() method so it looks at Shoelace form controls\n      if (!reportValidityOverloads.has(this.form)) {\n        reportValidityOverloads.set(this.form, this.form.reportValidity);\n        this.form.reportValidity = () => this.reportFormValidity();\n      }\n\n      // Overload the form's checkValidity() method so it looks at Shoelace form controls\n      if (!checkValidityOverloads.has(this.form)) {\n        checkValidityOverloads.set(this.form, this.form.checkValidity);\n        this.form.checkValidity = () => this.checkFormValidity();\n      }\n    } else {\n      this.form = undefined;\n    }\n  }\n\n  private detachForm() {\n    if (!this.form) return;\n\n    const formCollection = formCollections.get(this.form);\n\n    if (!formCollection) {\n      return;\n    }\n\n    // Remove this host from the form's collection\n    formCollection.delete(this.host);\n\n    // Check to make sure there's no other form controls in the collection. If we do this\n    // without checking if any other controls are still in the collection, then we will wipe out the\n    // validity checks for all other elements.\n    // see: https://github.com/shoelace-style/shoelace/issues/1703\n    if (formCollection.size <= 0) {\n      this.form.removeEventListener('formdata', this.handleFormData);\n      this.form.removeEventListener('submit', this.handleFormSubmit);\n      this.form.removeEventListener('reset', this.handleFormReset);\n\n      // Remove the overload and restore the original method\n      if (reportValidityOverloads.has(this.form)) {\n        this.form.reportValidity = reportValidityOverloads.get(this.form)!;\n        reportValidityOverloads.delete(this.form);\n      }\n\n      if (checkValidityOverloads.has(this.form)) {\n        this.form.checkValidity = checkValidityOverloads.get(this.form)!;\n        checkValidityOverloads.delete(this.form);\n      }\n\n      // So it looks weird here to not always set the form to undefined. But I _think_ if we unattach this.form here,\n      // we end up in this fun spot where future validity checks don't have a reference to the form validity handler.\n      // First form element in sets the validity handler. So we can't clean up `this.form` until there are no other form elements in the form.\n      this.form = undefined;\n    }\n  }\n\n  private handleFormData = (event: FormDataEvent) => {\n    const disabled = this.options.disabled(this.host);\n    const name = this.options.name(this.host);\n    const value = this.options.value(this.host);\n\n    // For buttons, we only submit the value if they were the submitter. This is currently done in doAction() by\n    // injecting the name/value on a temporary button, so we can just skip them here.\n    const isButton = this.host.tagName.toLowerCase() === 'sl-button';\n\n    if (\n      this.host.isConnected &&\n      !disabled &&\n      !isButton &&\n      typeof name === 'string' &&\n      name.length > 0 &&\n      typeof value !== 'undefined'\n    ) {\n      if (Array.isArray(value)) {\n        (value as unknown[]).forEach(val => {\n          event.formData.append(name, (val as string | number | boolean).toString());\n        });\n      } else {\n        event.formData.append(name, (value as string | number | boolean).toString());\n      }\n    }\n  };\n\n  private handleFormSubmit = (event: Event) => {\n    const disabled = this.options.disabled(this.host);\n    const reportValidity = this.options.reportValidity;\n\n    // Update the interacted state for all controls when the form is submitted\n    if (this.form && !this.form.noValidate) {\n      formCollections.get(this.form)?.forEach(control => {\n        this.setUserInteracted(control, true);\n      });\n    }\n\n    if (this.form && !this.form.noValidate && !disabled && !reportValidity(this.host)) {\n      event.preventDefault();\n      event.stopImmediatePropagation();\n    }\n  };\n\n  private handleFormReset = () => {\n    this.options.setValue(this.host, this.options.defaultValue(this.host));\n    this.setUserInteracted(this.host, false);\n    interactions.set(this.host, []);\n  };\n\n  private handleInteraction = (event: Event) => {\n    const emittedEvents = interactions.get(this.host)!;\n\n    if (!emittedEvents.includes(event.type)) {\n      emittedEvents.push(event.type);\n    }\n\n    // Mark it as user-interacted as soon as all associated events have been emitted\n    if (emittedEvents.length === this.options.assumeInteractionOn.length) {\n      this.setUserInteracted(this.host, true);\n    }\n  };\n\n  private checkFormValidity = () => {\n    //\n    // This is very similar to the `reportFormValidity` function, but it does not trigger native constraint validation\n    // Allow the user to simply check if the form is valid and handling validity in their own way.\n    //\n    // We preserve the original method in a WeakMap, but we don't call it from the overload because that would trigger\n    // validations in an unexpected order. When the element disconnects, we revert to the original behavior. This won't\n    // be necessary once we can use ElementInternals.\n    //\n    // Note that we're also honoring the form's novalidate attribute.\n    //\n    if (this.form && !this.form.noValidate) {\n      // This seems sloppy, but checking all elements will cover native inputs, Shoelace inputs, and other custom\n      // elements that support the constraint validation API.\n      const elements = this.form.querySelectorAll<HTMLInputElement>('*');\n\n      for (const element of elements) {\n        if (typeof element.checkValidity === 'function') {\n          if (!element.checkValidity()) {\n            return false;\n          }\n        }\n      }\n    }\n\n    return true;\n  };\n\n  private reportFormValidity = () => {\n    //\n    // Shoelace form controls work hard to act like regular form controls. They support the Constraint Validation API\n    // and its associated methods such as setCustomValidity() and reportValidity(). However, the HTMLFormElement also\n    // has a reportValidity() method that will trigger validation on all child controls. Since we're not yet using\n    // ElementInternals, we need to overload this method so it looks for any element with the reportValidity() method.\n    //\n    // We preserve the original method in a WeakMap, but we don't call it from the overload because that would trigger\n    // validations in an unexpected order. When the element disconnects, we revert to the original behavior. This won't\n    // be necessary once we can use ElementInternals.\n    //\n    // Note that we're also honoring the form's novalidate attribute.\n    //\n    if (this.form && !this.form.noValidate) {\n      // This seems sloppy, but checking all elements will cover native inputs, Shoelace inputs, and other custom\n      // elements that support the constraint validation API.\n      const elements = this.form.querySelectorAll<HTMLInputElement>('*');\n\n      for (const element of elements) {\n        if (typeof element.reportValidity === 'function') {\n          if (!element.reportValidity()) {\n            return false;\n          }\n        }\n      }\n    }\n\n    return true;\n  };\n\n  private setUserInteracted(el: ShoelaceFormControl, hasInteracted: boolean) {\n    if (hasInteracted) {\n      userInteractedControls.add(el);\n    } else {\n      userInteractedControls.delete(el);\n    }\n\n    el.requestUpdate();\n  }\n\n  private doAction(type: 'submit' | 'reset', submitter?: HTMLInputElement | SlButton) {\n    if (this.form) {\n      const button = document.createElement('button');\n      button.type = type;\n      button.style.position = 'absolute';\n      button.style.width = '0';\n      button.style.height = '0';\n      button.style.clipPath = 'inset(50%)';\n      button.style.overflow = 'hidden';\n      button.style.whiteSpace = 'nowrap';\n\n      // Pass name, value, and form attributes through to the temporary button\n      if (submitter) {\n        button.name = submitter.name;\n        button.value = submitter.value;\n\n        ['formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget'].forEach(attr => {\n          if (submitter.hasAttribute(attr)) {\n            button.setAttribute(attr, submitter.getAttribute(attr)!);\n          }\n        });\n      }\n\n      this.form.append(button);\n      button.click();\n      button.remove();\n    }\n  }\n\n  /** Returns the associated `<form>` element, if one exists. */\n  getForm() {\n    return this.form ?? null;\n  }\n\n  /** Resets the form, restoring all the control to their default value */\n  reset(submitter?: HTMLInputElement | SlButton) {\n    this.doAction('reset', submitter);\n  }\n\n  /** Submits the form, triggering validation and form data injection. */\n  submit(submitter?: HTMLInputElement | SlButton) {\n    // Calling form.submit() bypasses the submit event and constraint validation. To prevent this, we can inject a\n    // native submit button into the form, \"click\" it, then remove it to simulate a standard form submission.\n    this.doAction('submit', submitter);\n  }\n\n  /**\n   * Synchronously sets the form control's validity. Call this when you know the future validity but need to update\n   * the host element immediately, i.e. before Lit updates the component in the next update.\n   */\n  setValidity(isValid: boolean) {\n    const host = this.host;\n    const hasInteracted = Boolean(userInteractedControls.has(host));\n    const required = Boolean(host.required);\n\n    //\n    // We're mapping the following \"states\" to data attributes. In the future, we can use ElementInternals.states to\n    // create a similar mapping, but instead of [data-invalid] it will look like :--invalid.\n    //\n    // See this RFC for more details: https://github.com/shoelace-style/shoelace/issues/1011\n    //\n    host.toggleAttribute('data-required', required);\n    host.toggleAttribute('data-optional', !required);\n    host.toggleAttribute('data-invalid', !isValid);\n    host.toggleAttribute('data-valid', isValid);\n    host.toggleAttribute('data-user-invalid', !isValid && hasInteracted);\n    host.toggleAttribute('data-user-valid', isValid && hasInteracted);\n  }\n\n  /**\n   * Updates the form control's validity based on the current value of `host.validity.valid`. Call this when anything\n   * that affects constraint validation changes so the component receives the correct validity states.\n   */\n  updateValidity() {\n    const host = this.host;\n    this.setValidity(host.validity.valid);\n  }\n\n  /**\n   * Dispatches a non-bubbling, cancelable custom event of type `sl-invalid`.\n   * If the `sl-invalid` event will be cancelled then the original `invalid`\n   * event (which may have been passed as argument) will also be cancelled.\n   * If no original `invalid` event has been passed then the `sl-invalid`\n   * event will be cancelled before being dispatched.\n   */\n  emitInvalidEvent(originalInvalidEvent?: Event) {\n    const slInvalidEvent = new CustomEvent<Record<PropertyKey, never>>('sl-invalid', {\n      bubbles: false,\n      composed: false,\n      cancelable: true,\n      detail: {}\n    });\n\n    if (!originalInvalidEvent) {\n      slInvalidEvent.preventDefault();\n    }\n\n    if (!this.host.dispatchEvent(slInvalidEvent)) {\n      originalInvalidEvent?.preventDefault();\n    }\n  }\n}\n\n/*\n * Predefined common validity states.\n * All of them are read-only.\n */\n\n// A validity state object that represents `valid`\nexport const validValidityState: ValidityState = Object.freeze({\n  badInput: false,\n  customError: false,\n  patternMismatch: false,\n  rangeOverflow: false,\n  rangeUnderflow: false,\n  stepMismatch: false,\n  tooLong: false,\n  tooShort: false,\n  typeMismatch: false,\n  valid: true,\n  valueMissing: false\n});\n\n// A validity state object that represents `value missing`\nexport const valueMissingValidityState: ValidityState = Object.freeze({\n  ...validValidityState,\n  valid: false,\n  valueMissing: true\n});\n\n// A validity state object that represents a custom error\nexport const customErrorValidityState: ValidityState = Object.freeze({\n  ...validValidityState,\n  valid: false,\n  customError: true\n});\n"
  },
  {
    "path": "src/internal/math.ts",
    "content": "/** Ensures a number stays within a minimum and maximum value */\nexport function clamp(value: number, min: number, max: number) {\n  const noNegativeZero = (n: number) => (Object.is(n, -0) ? 0 : n);\n\n  if (value < min) {\n    return noNegativeZero(min);\n  }\n\n  if (value > max) {\n    return noNegativeZero(max);\n  }\n\n  return noNegativeZero(value);\n}\n"
  },
  {
    "path": "src/internal/modal.ts",
    "content": "import { activeElements, getDeepestActiveElement } from './active-elements.js';\nimport { getTabbableElements } from './tabbable.js';\n\nlet activeModals: HTMLElement[] = [];\n\nexport default class Modal {\n  element: HTMLElement;\n  isExternalActivated: boolean;\n  tabDirection: 'forward' | 'backward' = 'forward';\n  currentFocus: HTMLElement | null;\n  previousFocus: HTMLElement | null;\n  elementsWithTabbableControls: string[];\n\n  constructor(element: HTMLElement) {\n    this.element = element;\n\n    this.elementsWithTabbableControls = ['iframe'];\n  }\n\n  /** Activates focus trapping. */\n  activate() {\n    activeModals.push(this.element);\n    document.addEventListener('focusin', this.handleFocusIn);\n    document.addEventListener('keydown', this.handleKeyDown);\n    document.addEventListener('keyup', this.handleKeyUp);\n  }\n\n  /** Deactivates focus trapping. */\n  deactivate() {\n    activeModals = activeModals.filter(modal => modal !== this.element);\n    this.currentFocus = null;\n    document.removeEventListener('focusin', this.handleFocusIn);\n    document.removeEventListener('keydown', this.handleKeyDown);\n    document.removeEventListener('keyup', this.handleKeyUp);\n  }\n\n  /** Determines if this modal element is currently active or not. */\n  isActive() {\n    // The \"active\" modal is always the most recent one shown\n    return activeModals[activeModals.length - 1] === this.element;\n  }\n\n  /** Activates external modal behavior and temporarily disables focus trapping. */\n  activateExternal() {\n    this.isExternalActivated = true;\n  }\n\n  /** Deactivates external modal behavior and re-enables focus trapping. */\n  deactivateExternal() {\n    this.isExternalActivated = false;\n  }\n\n  private checkFocus() {\n    if (this.isActive() && !this.isExternalActivated) {\n      const tabbableElements = getTabbableElements(this.element);\n      if (!this.element.matches(':focus-within')) {\n        const start = tabbableElements[0];\n        const end = tabbableElements[tabbableElements.length - 1];\n        const target = this.tabDirection === 'forward' ? start : end;\n\n        if (typeof target?.focus === 'function') {\n          this.currentFocus = target;\n          target.focus({ preventScroll: false });\n        }\n      }\n    }\n  }\n\n  private handleFocusIn = () => {\n    if (!this.isActive()) return;\n    this.checkFocus();\n  };\n\n  private possiblyHasTabbableChildren(element: HTMLElement) {\n    return (\n      this.elementsWithTabbableControls.includes(element.tagName.toLowerCase()) || element.hasAttribute('controls')\n      // Should we add a data-attribute for people to set just in case they have an element where we don't know if it has possibly tabbable elements?\n    );\n  }\n\n  private handleKeyDown = (event: KeyboardEvent) => {\n    if (event.key !== 'Tab' || this.isExternalActivated) return;\n    if (!this.isActive()) return;\n\n    // Because sometimes focus can actually be taken over from outside sources,\n    // we don't want to rely on `this.currentFocus`. Instead we check the actual `activeElement` and\n    // recurse through shadowRoots.\n    const currentActiveElement = getDeepestActiveElement();\n    this.previousFocus = currentActiveElement as HTMLElement | null;\n\n    if (this.previousFocus && this.possiblyHasTabbableChildren(this.previousFocus)) {\n      return;\n    }\n\n    if (event.shiftKey) {\n      this.tabDirection = 'backward';\n    } else {\n      this.tabDirection = 'forward';\n    }\n\n    const tabbableElements = getTabbableElements(this.element);\n\n    let currentFocusIndex = tabbableElements.findIndex(el => el === currentActiveElement);\n\n    this.previousFocus = this.currentFocus;\n\n    const addition = this.tabDirection === 'forward' ? 1 : -1;\n\n    // eslint-disable-next-line\n    while (true) {\n      if (currentFocusIndex + addition >= tabbableElements.length) {\n        currentFocusIndex = 0;\n      } else if (currentFocusIndex + addition < 0) {\n        currentFocusIndex = tabbableElements.length - 1;\n      } else {\n        currentFocusIndex += addition;\n      }\n\n      this.previousFocus = this.currentFocus;\n      const nextFocus = /** @type {HTMLElement} */ tabbableElements[currentFocusIndex];\n\n      // This is a special case. We need to make sure we're not calling .focus() if we're already focused on an element\n      // that possibly has \"controls\"\n      if (this.tabDirection === 'backward') {\n        if (this.previousFocus && this.possiblyHasTabbableChildren(this.previousFocus)) {\n          return;\n        }\n      }\n\n      if (nextFocus && this.possiblyHasTabbableChildren(nextFocus)) {\n        return;\n      }\n\n      event.preventDefault();\n      this.currentFocus = nextFocus;\n      this.currentFocus?.focus({ preventScroll: false });\n\n      // Check to make sure focus actually changed. It may not always be the next focus, we just don't want it to be the previousFocus.\n      const allActiveElements = [...activeElements()];\n      if (allActiveElements.includes(this.currentFocus) || !allActiveElements.includes(this.previousFocus!)) {\n        break;\n      }\n    }\n\n    setTimeout(() => this.checkFocus());\n  };\n\n  private handleKeyUp = () => {\n    this.tabDirection = 'forward';\n  };\n}\n"
  },
  {
    "path": "src/internal/offset.ts",
    "content": "/**\n * Returns an element's offset relative to its parent. Similar to element.offsetTop and element.offsetLeft, except the\n * parent doesn't have to be positioned relative or absolute.\n *\n * NOTE: This was created to work around what appears to be a bug in Chrome where a slotted element's offsetParent seems\n * to ignore elements inside the surrounding shadow DOM: https://bugs.chromium.org/p/chromium/issues/detail?id=920069\n */\nexport function getOffset(element: HTMLElement, parent: HTMLElement) {\n  return {\n    top: Math.round(element.getBoundingClientRect().top - parent.getBoundingClientRect().top),\n    left: Math.round(element.getBoundingClientRect().left - parent.getBoundingClientRect().left)\n  };\n}\n"
  },
  {
    "path": "src/internal/scroll.ts",
    "content": "import { getOffset } from './offset.js';\n\nconst locks = new Set();\n\n/** Returns the width of the document's scrollbar */\nfunction getScrollbarWidth() {\n  const documentWidth = document.documentElement.clientWidth;\n  return Math.abs(window.innerWidth - documentWidth);\n}\n\n/**\n * Used in conjunction with `scrollbarWidth` to set proper body padding in case the user has padding already on the `<body>` element.\n */\nfunction getExistingBodyPadding() {\n  const padding = Number(getComputedStyle(document.body).paddingRight.replace(/px/, ''));\n\n  if (isNaN(padding) || !padding) {\n    return 0;\n  }\n\n  return padding;\n}\n\n/**\n * Prevents body scrolling. Keeps track of which elements requested a lock so multiple levels of locking are possible\n * without premature unlocking.\n */\nexport function lockBodyScrolling(lockingEl: HTMLElement) {\n  locks.add(lockingEl);\n\n  // When the first lock is created, set the scroll lock size to match the scrollbar's width to prevent content from\n  // shifting. We only do this on the first lock because the scrollbar width will measure zero after overflow is hidden.\n  if (!document.documentElement.classList.contains('sl-scroll-lock')) {\n    /** Scrollbar width + body padding calculation can go away once Safari has scrollbar-gutter support. */\n    const scrollbarWidth = getScrollbarWidth() + getExistingBodyPadding(); // must be measured before the `sl-scroll-lock` class is applied\n\n    let scrollbarGutterProperty = getComputedStyle(document.documentElement).scrollbarGutter;\n\n    // default is auto, unsupported browsers is \"undefined\"\n    if (!scrollbarGutterProperty || scrollbarGutterProperty === 'auto') {\n      scrollbarGutterProperty = 'stable';\n    }\n\n    /** Sometimes the scrollbar width is 1px, even then, we assume nothing is overflowing. */\n    if (scrollbarWidth < 2) {\n      // if there's no scrollbar, just set it to an empty string so whatever the user has set gets used. This is useful if the page is not overflowing and showing a scrollbar, or if the user has overflow: hidden, or any other reason a scrollbar may not be showing.\n      scrollbarGutterProperty = '';\n    }\n    document.documentElement.style.setProperty('--sl-scroll-lock-gutter', scrollbarGutterProperty);\n    document.documentElement.classList.add('sl-scroll-lock');\n    document.documentElement.style.setProperty('--sl-scroll-lock-size', `${scrollbarWidth}px`);\n  }\n}\n\n/**\n * Unlocks body scrolling. Scrolling will only be unlocked once all elements that requested a lock call this method.\n */\nexport function unlockBodyScrolling(lockingEl: HTMLElement) {\n  locks.delete(lockingEl);\n\n  if (locks.size === 0) {\n    document.documentElement.classList.remove('sl-scroll-lock');\n    document.documentElement.style.removeProperty('--sl-scroll-lock-size');\n  }\n}\n\n/** Scrolls an element into view of its container. If the element is already in view, nothing will happen. */\nexport function scrollIntoView(\n  element: HTMLElement,\n  container: HTMLElement,\n  direction: 'horizontal' | 'vertical' | 'both' = 'vertical',\n  behavior: 'smooth' | 'auto' = 'smooth'\n) {\n  const offset = getOffset(element, container);\n  const offsetTop = offset.top + container.scrollTop;\n  const offsetLeft = offset.left + container.scrollLeft;\n  const minX = container.scrollLeft;\n  const maxX = container.scrollLeft + container.offsetWidth;\n  const minY = container.scrollTop;\n  const maxY = container.scrollTop + container.offsetHeight;\n\n  if (direction === 'horizontal' || direction === 'both') {\n    if (offsetLeft < minX) {\n      container.scrollTo({ left: offsetLeft, behavior });\n    } else if (offsetLeft + element.clientWidth > maxX) {\n      container.scrollTo({ left: offsetLeft - container.offsetWidth + element.clientWidth, behavior });\n    }\n  }\n\n  if (direction === 'vertical' || direction === 'both') {\n    if (offsetTop < minY) {\n      container.scrollTo({ top: offsetTop, behavior });\n    } else if (offsetTop + element.clientHeight > maxY) {\n      container.scrollTo({ top: offsetTop - container.offsetHeight + element.clientHeight, behavior });\n    }\n  }\n}\n"
  },
  {
    "path": "src/internal/scrollend-polyfill.ts",
    "content": "type GenericCallback = (this: unknown, ...args: unknown[]) => unknown;\n\ntype MethodOf<T, K extends keyof T> = T[K] extends GenericCallback ? T[K] : never;\n\nconst debounce = <T extends GenericCallback>(fn: T, delay: number) => {\n  let timerId = 0;\n\n  return function (this: unknown, ...args: unknown[]) {\n    window.clearTimeout(timerId);\n    timerId = window.setTimeout(() => {\n      fn.call(this, ...args);\n    }, delay);\n  };\n};\n\nconst decorate = <T, M extends keyof T>(\n  proto: T,\n  method: M,\n  decorateFn: (this: unknown, superFn: T[M], ...args: unknown[]) => unknown\n) => {\n  const superFn = proto[method] as MethodOf<T, M>;\n\n  proto[method] = function (this: unknown, ...args: unknown[]) {\n    superFn.call(this, ...args);\n    decorateFn.call(this, superFn, ...args);\n  } as MethodOf<T, M>;\n};\n\n(() => {\n  // SSR environments should not apply the polyfill\n  if (typeof window === 'undefined') {\n    return;\n  }\n\n  const isSupported = 'onscrollend' in window;\n\n  if (!isSupported) {\n    const pointers = new Set();\n    const scrollHandlers = new WeakMap<EventTarget, EventListenerOrEventListenerObject>();\n\n    const handlePointerDown = (event: TouchEvent) => {\n      for (const touch of event.changedTouches) {\n        pointers.add(touch.identifier);\n      }\n    };\n\n    const handlePointerUp = (event: TouchEvent) => {\n      for (const touch of event.changedTouches) {\n        pointers.delete(touch.identifier);\n      }\n    };\n\n    document.addEventListener('touchstart', handlePointerDown, true);\n    document.addEventListener('touchend', handlePointerUp, true);\n    document.addEventListener('touchcancel', handlePointerUp, true);\n\n    decorate(EventTarget.prototype, 'addEventListener', function (this: EventTarget, addEventListener, type) {\n      if (type !== 'scrollend') return;\n\n      const handleScrollEnd = debounce(() => {\n        if (!pointers.size) {\n          // If no pointer is active in the scroll area then the scroll has ended\n          this.dispatchEvent(new Event('scrollend'));\n        } else {\n          // otherwise let's wait a bit more\n          handleScrollEnd();\n        }\n      }, 100);\n\n      addEventListener.call(this, 'scroll', handleScrollEnd, { passive: true });\n      scrollHandlers.set(this, handleScrollEnd);\n    });\n\n    decorate(EventTarget.prototype, 'removeEventListener', function (this: EventTarget, removeEventListener, type) {\n      if (type !== 'scrollend') return;\n\n      const scrollHandler = scrollHandlers.get(this);\n      if (scrollHandler) {\n        removeEventListener.call(this, 'scroll', scrollHandler, { passive: true } as unknown as EventListenerOptions);\n      }\n    });\n  }\n})();\n\n// Without an import or export, TypeScript sees vars in this file as global\nexport {};\n"
  },
  {
    "path": "src/internal/shoelace-element.test.ts",
    "content": "// TODO: Write logic around custom element definitions, no defs, conflicting defs, same defs, etc.\nimport { expect } from '@open-wc/testing';\nimport { readFile } from '@web/test-runner-commands';\n\nimport SlButton from '../../dist/components/button/button.component.js';\n\n// We don't use ShoelaceElement directly because it shouldn't exist in the final bundle.\n/* eslint-disable */\nconst ShoelaceElement = Object.getPrototypeOf(SlButton);\n/* eslint-enable */\n\n// @ts-expect-error Isn't written in TS.\nimport { getAllComponents } from '../../scripts/shared.js';\n\nimport Sinon from 'sinon';\n\nconst getMetadata = () => readFile({ path: '../../dist/custom-elements.json' }) as unknown as Promise<string>;\n\nlet counter = 0;\n\n// These tests all run in the same tab so they pollute the global custom element registry.\n// Some tests use this stub to be able to just test registration.\nfunction stubCustomElements() {\n  const map = new Map<string, CustomElementConstructor>();\n\n  Sinon.stub(window.customElements, 'get').callsFake(str => {\n    return map.get(str);\n  });\n\n  const stub = Sinon.stub(window.customElements, 'define');\n  stub.callsFake((str, ctor) => {\n    if (map.get(str)) {\n      return;\n    }\n\n    // Assign it a random string so it doesn't pollute globally.\n    const randomTagName = str + '-' + counter.toString();\n    counter++;\n    stub.wrappedMethod.apply(window.customElements, [randomTagName, ctor]);\n    map.set(str, ctor);\n  });\n}\n\nbeforeEach(() => {\n  Sinon.restore();\n  stubCustomElements();\n});\n\nit('Should provide a console warning if attempting to register the same tag twice', () => {\n  class MyButton extends SlButton {\n    static version = '0.4.5';\n  }\n\n  const stub = Sinon.stub(console, 'warn');\n\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.false;\n  /* eslint-disable */\n  SlButton.define('sl-button');\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.true;\n  MyButton.define('sl-button');\n  /* eslint-enable */\n\n  expect(stub).calledOnce;\n\n  const warning = stub.getCall(0).args.join('');\n\n  expect(warning).to.match(\n    new RegExp(\n      /* eslint-disable */\n      `Attempted to register <sl-button> v${MyButton.version}, but <sl-button> v${SlButton.version} has already been registered`\n      /* eslint-enable */\n    ),\n    'i'\n  );\n});\n\nit('Should not provide a console warning if versions match', () => {\n  class MyButton extends SlButton {}\n\n  const stub = Sinon.stub(console, 'warn');\n\n  /* eslint-disable */\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.false;\n  SlButton.define('sl-button');\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.true;\n  MyButton.define('sl-button');\n  /* eslint-enable */\n\n  expect(stub).not.called;\n});\n\nit('Should register dependencies when the element is constructed the first time', () => {\n  /* eslint-disable */\n  class MyElement extends ShoelaceElement {\n    static dependencies = { 'sl-button': SlButton };\n    static version = 'random-version';\n  }\n  /* eslint-enable */\n\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.false;\n\n  // eslint-disable\n  MyElement.define('sl-element');\n  // eslint-enable\n\n  // this should be false until the constructor is called via new\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.false;\n\n  // We can call it directly since we know its registered.\n  /* eslint-disable */\n  // @ts-expect-error If its undefined, error.\n  new (window.customElements.get('sl-element'))();\n  /* eslint-enable */\n\n  expect(Boolean(window.customElements.get('sl-button'))).to.be.true;\n});\n\n// This looks funky here. This grabs all of our components and tests for side effects.\n// We \"abuse\" mocha and dynamically define tests.\nbefore(async () => {\n  const metadata = JSON.parse(await getMetadata()) as Record<string, unknown>;\n\n  const tagNames: string[] = [];\n\n  const relevantMetadata: { tagName: string; path: string }[] = getAllComponents(metadata).map(\n    (component: { tagName: string; path: string }) => {\n      const { tagName, path } = component;\n      tagNames.push(tagName);\n\n      return { tagName, path };\n    }\n  );\n\n  relevantMetadata.forEach(({ tagName, path }) => {\n    it(`Should not register any components: ${tagName}`, async () => {\n      await import('../../dist/' + path);\n\n      // Need to make sure we remove the current tag from the tagNames and *then* see whats been registered.\n      const registeredTags = tagNames.filter(tag => tag !== tagName && Boolean(window.customElements.get(tag)));\n\n      const errorMessage =\n        `Expected ${path} to not register any tags, but it registered the following tags: ` +\n        registeredTags.map(tag => tag).join(', ');\n      expect(registeredTags.length).to.equal(0, errorMessage);\n    });\n  });\n});\n"
  },
  {
    "path": "src/internal/shoelace-element.ts",
    "content": "import { LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\n\n// Match event type name strings that are registered on GlobalEventHandlersEventMap...\ntype EventTypeRequiresDetail<T> = T extends keyof GlobalEventHandlersEventMap\n  ? // ...where the event detail is an object...\n    GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, unknown>>\n    ? // ...that is non-empty...\n      GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, never>>\n      ? never\n      : // ...and has at least one non-optional property\n        Partial<GlobalEventHandlersEventMap[T]['detail']> extends GlobalEventHandlersEventMap[T]['detail']\n        ? never\n        : T\n    : never\n  : never;\n\n// The inverse of the above (match any type that doesn't match EventTypeRequiresDetail)\ntype EventTypeDoesNotRequireDetail<T> = T extends keyof GlobalEventHandlersEventMap\n  ? GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, unknown>>\n    ? GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, never>>\n      ? T\n      : Partial<GlobalEventHandlersEventMap[T]['detail']> extends GlobalEventHandlersEventMap[T]['detail']\n        ? T\n        : never\n    : T\n  : T;\n\n// `keyof EventTypesWithRequiredDetail` lists all registered event types that require detail\ntype EventTypesWithRequiredDetail = {\n  [EventType in keyof GlobalEventHandlersEventMap as EventTypeRequiresDetail<EventType>]: true;\n};\n\n// `keyof EventTypesWithoutRequiredDetail` lists all registered event types that do NOT require detail\ntype EventTypesWithoutRequiredDetail = {\n  [EventType in keyof GlobalEventHandlersEventMap as EventTypeDoesNotRequireDetail<EventType>]: true;\n};\n\n// Helper to make a specific property of an object non-optional\ntype WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };\n\n// Given an event name string, get a valid type for the options to initialize the event that is more restrictive than\n// just CustomEventInit when appropriate (validate the type of the event detail, and require it to be provided if the\n// event requires it)\ntype SlEventInit<T> = T extends keyof GlobalEventHandlersEventMap\n  ? GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, unknown>>\n    ? GlobalEventHandlersEventMap[T] extends CustomEvent<Record<PropertyKey, never>>\n      ? CustomEventInit<GlobalEventHandlersEventMap[T]['detail']>\n      : Partial<GlobalEventHandlersEventMap[T]['detail']> extends GlobalEventHandlersEventMap[T]['detail']\n        ? CustomEventInit<GlobalEventHandlersEventMap[T]['detail']>\n        : WithRequired<CustomEventInit<GlobalEventHandlersEventMap[T]['detail']>, 'detail'>\n    : CustomEventInit\n  : CustomEventInit;\n\n// Given an event name string, get the type of the event\ntype GetCustomEventType<T> = T extends keyof GlobalEventHandlersEventMap\n  ? GlobalEventHandlersEventMap[T] extends CustomEvent<unknown>\n    ? GlobalEventHandlersEventMap[T]\n    : CustomEvent<unknown>\n  : CustomEvent<unknown>;\n\n// `keyof ValidEventTypeMap` is equivalent to `keyof GlobalEventHandlersEventMap` but gives a nicer error message\ntype ValidEventTypeMap = EventTypesWithRequiredDetail | EventTypesWithoutRequiredDetail;\n\nexport default class ShoelaceElement extends LitElement {\n  // Make localization attributes reactive\n  @property() dir: string;\n  @property() lang: string;\n\n  /** Emits a custom event with more convenient defaults. */\n  emit<T extends string & keyof EventTypesWithoutRequiredDetail>(\n    name: EventTypeDoesNotRequireDetail<T>,\n    options?: SlEventInit<T> | undefined\n  ): GetCustomEventType<T>;\n  emit<T extends string & keyof EventTypesWithRequiredDetail>(\n    name: EventTypeRequiresDetail<T>,\n    options: SlEventInit<T>\n  ): GetCustomEventType<T>;\n  emit<T extends string & keyof ValidEventTypeMap>(\n    name: T,\n    options?: SlEventInit<T> | undefined\n  ): GetCustomEventType<T> {\n    const event = new CustomEvent(name, {\n      bubbles: true,\n      cancelable: false,\n      composed: true,\n      detail: {},\n      ...options\n    });\n\n    this.dispatchEvent(event);\n\n    return event as GetCustomEventType<T>;\n  }\n\n  /* eslint-disable */\n  // @ts-expect-error This is auto-injected at build time.\n  static version = __SHOELACE_VERSION__;\n  /* eslint-enable */\n\n  static define(name: string, elementConstructor = this, options: ElementDefinitionOptions = {}) {\n    const currentlyRegisteredConstructor = customElements.get(name) as\n      | CustomElementConstructor\n      | typeof ShoelaceElement;\n\n    if (!currentlyRegisteredConstructor) {\n      // We try to register as the actual class first. If for some reason that fails, we fall back to anonymous classes.\n      // customElements can only have 1 class of the same \"object id\" per registry, so that is why the try {} catch {} exists.\n      // Some tools like Jest Snapshots and if you import the constructor and call `new SlButton()` they will fail with\n      //   the anonymous class version.\n      try {\n        customElements.define(name, elementConstructor, options);\n      } catch (_err) {\n        customElements.define(name, class extends elementConstructor {}, options);\n      }\n      return;\n    }\n\n    let newVersion = ' (unknown version)';\n    let existingVersion = newVersion;\n\n    if ('version' in elementConstructor && elementConstructor.version) {\n      newVersion = ' v' + elementConstructor.version;\n    }\n\n    if ('version' in currentlyRegisteredConstructor && currentlyRegisteredConstructor.version) {\n      existingVersion = ' v' + currentlyRegisteredConstructor.version;\n    }\n\n    // Need to make sure we're not working with null or empty strings before doing version comparisons.\n    if (newVersion && existingVersion && newVersion === existingVersion) {\n      // If versions match, we don't need to warn anyone. Carry on.\n      return;\n    }\n\n    console.warn(\n      `Attempted to register <${name}>${newVersion}, but <${name}>${existingVersion} has already been registered.`\n    );\n  }\n\n  static dependencies: Record<string, typeof ShoelaceElement> = {};\n\n  constructor() {\n    super();\n    Object.entries((this.constructor as typeof ShoelaceElement).dependencies).forEach(([name, component]) => {\n      (this.constructor as typeof ShoelaceElement).define(name, component);\n    });\n  }\n\n  #hasRecordedInitialProperties = false;\n\n  // Store the constructor value of all `static properties = {}`\n  initialReflectedProperties: Map<string, unknown> = new Map();\n\n  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n    if (!this.#hasRecordedInitialProperties) {\n      (this.constructor as typeof ShoelaceElement).elementProperties.forEach(\n        (obj, prop: keyof typeof this & string) => {\n          // eslint-disable-next-line\n          if (obj.reflect && this[prop] != null) {\n            this.initialReflectedProperties.set(prop, this[prop]);\n          }\n        }\n      );\n\n      this.#hasRecordedInitialProperties = true;\n    }\n\n    super.attributeChangedCallback(name, oldValue, newValue);\n  }\n\n  protected willUpdate(changedProperties: Parameters<LitElement['willUpdate']>[0]): void {\n    super.willUpdate(changedProperties);\n\n    // Run the morph fixing *after* willUpdate.\n    this.initialReflectedProperties.forEach((value, prop: string & keyof typeof this) => {\n      // If a prop changes to `null`, we assume this happens via an attribute changing to `null`.\n      // eslint-disable-next-line\n      if (changedProperties.has(prop) && this[prop] == null) {\n        // Silly type gymnastics to appease the compiler.\n        (this as Record<string, unknown>)[prop] = value;\n      }\n    });\n  }\n}\n\nexport interface ShoelaceFormControl extends ShoelaceElement {\n  // Form attributes\n  name: string;\n  value: unknown;\n  disabled?: boolean;\n  defaultValue?: unknown;\n  defaultChecked?: boolean;\n  form?: string;\n\n  // Constraint validation attributes\n  pattern?: string;\n  min?: number | string | Date;\n  max?: number | string | Date;\n  step?: number | 'any';\n  required?: boolean;\n  minlength?: number;\n  maxlength?: number;\n\n  // Form validation properties\n  readonly validity: ValidityState;\n  readonly validationMessage: string;\n\n  // Form validation methods\n  checkValidity: () => boolean;\n  getForm: () => HTMLFormElement | null;\n  reportValidity: () => boolean;\n  setCustomValidity: (message: string) => void;\n}\n"
  },
  {
    "path": "src/internal/slot.ts",
    "content": "import type { ReactiveController, ReactiveControllerHost } from 'lit';\n\n/** A reactive controller that determines when slots exist. */\nexport class HasSlotController implements ReactiveController {\n  host: ReactiveControllerHost & Element;\n  slotNames: string[] = [];\n\n  constructor(host: ReactiveControllerHost & Element, ...slotNames: string[]) {\n    (this.host = host).addController(this);\n    this.slotNames = slotNames;\n  }\n\n  private hasDefaultSlot() {\n    return [...this.host.childNodes].some(node => {\n      if (node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '') {\n        return true;\n      }\n\n      if (node.nodeType === node.ELEMENT_NODE) {\n        const el = node as HTMLElement;\n        const tagName = el.tagName.toLowerCase();\n\n        // Ignore visually hidden elements since they aren't rendered\n        if (tagName === 'sl-visually-hidden') {\n          return false;\n        }\n\n        // If it doesn't have a slot attribute, it's part of the default slot\n        if (!el.hasAttribute('slot')) {\n          return true;\n        }\n      }\n\n      return false;\n    });\n  }\n\n  private hasNamedSlot(name: string) {\n    return this.host.querySelector(`:scope > [slot=\"${name}\"]`) !== null;\n  }\n\n  test(slotName: string) {\n    return slotName === '[default]' ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);\n  }\n\n  hostConnected() {\n    this.host.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);\n  }\n\n  hostDisconnected() {\n    this.host.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);\n  }\n\n  private handleSlotChange = (event: Event) => {\n    const slot = event.target as HTMLSlotElement;\n\n    if ((this.slotNames.includes('[default]') && !slot.name) || (slot.name && this.slotNames.includes(slot.name))) {\n      this.host.requestUpdate();\n    }\n  };\n}\n\n/**\n * Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated\n * HTML as a string. This is useful because we can't use slot.innerHTML as an alternative.\n */\nexport function getInnerHTML(slot: HTMLSlotElement): string {\n  const nodes = slot.assignedNodes({ flatten: true });\n  let html = '';\n\n  [...nodes].forEach(node => {\n    if (node.nodeType === Node.ELEMENT_NODE) {\n      html += (node as HTMLElement).outerHTML;\n    }\n\n    if (node.nodeType === Node.TEXT_NODE) {\n      html += node.textContent;\n    }\n  });\n\n  return html;\n}\n\n/**\n * Given a slot, this function iterates over all of its assigned text nodes and returns the concatenated text as a\n * string. This is useful because we can't use slot.textContent as an alternative.\n */\nexport function getTextContent(slot: HTMLSlotElement | undefined | null): string {\n  if (!slot) {\n    return '';\n  }\n  const nodes = slot.assignedNodes({ flatten: true });\n  let text = '';\n\n  [...nodes].forEach(node => {\n    if (node.nodeType === Node.TEXT_NODE) {\n      text += node.textContent;\n    }\n  });\n\n  return text;\n}\n"
  },
  {
    "path": "src/internal/string.ts",
    "content": "/** Converts the first letter of a string to uppercase */\nexport function uppercaseFirstLetter(string: string) {\n  return string.charAt(0).toUpperCase() + string.slice(1);\n}\n"
  },
  {
    "path": "src/internal/tabbable.test.ts",
    "content": "import { aTimeout, elementUpdated, expect, fixture } from '@open-wc/testing';\n\nimport { activeElements, getDeepestActiveElement } from './active-elements.js';\nimport { clickOnElement } from './test.js';\nimport { html } from 'lit';\nimport { sendKeys } from '@web/test-runner-commands';\nimport type { SlDialog } from '../shoelace.js';\n\nimport '../../../dist/shoelace.js';\n\nasync function holdShiftKey(callback: () => Promise<void>) {\n  await sendKeys({ down: 'Shift' });\n  await callback();\n  await sendKeys({ up: 'Shift' });\n}\n\nconst tabKey =\n  navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('HeadlessChrome') ? 'Alt+Tab' : 'Tab';\n\n// Simple helper to turn the activeElements generator into an array\nfunction activeElementsArray() {\n  return [...activeElements()];\n}\n\nwindow.customElements.define(\n  'tab-test-1',\n  class extends HTMLElement {\n    constructor() {\n      super();\n      this.attachShadow({ mode: 'open' });\n    }\n    connectedCallback() {\n      this.shadowRoot!.innerHTML = `\n      <sl-drawer>\n        <slot name=\"label\" slot=\"label\"></slot>\n\n        <slot></slot>\n\n        <slot name=\"footer\" slot=\"footer\"></slot>\n      </sl-drawer>\n    `;\n    }\n  }\n);\n\nit('Should allow tabbing to slotted elements', async () => {\n  const el = await fixture(html`\n    <tab-test-1>\n      <div slot=\"label\">\n        <sl-button id=\"focus-1\">Focus 1</sl-button>\n      </div>\n\n      <div>\n        <!-- Focus 2 lives as the close-button from <sl-drawer> -->\n        <sl-button id=\"focus-3\">Focus 3</sl-button>\n        <button id=\"focus-4\">Focus 4</sl-button>\n        <input id=\"focus-5\" value=\"Focus 5\">\n      </div>\n\n      <div slot=\"footer\">\n        <div id=\"focus-6\" tabindex=\"0\">Focus 6</div>\n        <button tabindex=\"-1\">No Focus</button>\n      </div>\n    </tab-test-1>\n  `);\n\n  const drawer = el.shadowRoot?.querySelector('sl-drawer');\n\n  if (drawer === null || drawer === undefined) throw Error('Could not find drawer inside of the test element');\n\n  await drawer.show();\n\n  await elementUpdated(drawer);\n\n  const focusZero = drawer.shadowRoot?.querySelector(\"[role='dialog']\");\n\n  if (focusZero === null || focusZero === undefined) throw Error('Could not find dialog panel inside <sl-drawer>');\n\n  const focusOne = el.querySelector('#focus-1');\n  const focusTwo = drawer.shadowRoot?.querySelector(\"[part~='close-button']\");\n\n  if (focusTwo === null || focusTwo === undefined) throw Error('Could not find close button inside <sl-drawer>');\n\n  const focusThree = el.querySelector('#focus-3');\n  const focusFour = el.querySelector('#focus-4');\n  const focusFive = el.querySelector('#focus-5');\n  const focusSix = el.querySelector('#focus-6');\n\n  // When we open drawer, we should be focused on the panel to start.\n  expect(getDeepestActiveElement()).to.equal(focusZero);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusOne);\n\n  // When we hit the <Tab> key we should go to the \"close button\" on the drawer\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusTwo);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusThree);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusFour);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusFive);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusSix);\n\n  // Now we should loop back to #panel\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusZero);\n\n  // Now we should loop back to #panel\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(focusOne);\n\n  // Let's reset and try from starting point 0 and go backwards.\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusZero);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusSix);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusFive);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusFour);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusThree);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusTwo);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusOne);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusZero);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(focusSix);\n});\n\nit.skip('Should account for when focus is changed from outside sources (like clicking)', async () => {\n  const dialog = await fixture(html`\n    <sl-dialog open=\"\" label=\"Dialog\" class=\"dialog-overview\">\n      Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n      <sl-input placeholder=\"tab to me\"></sl-input>\n      <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n    </sl-dialog>\n  `);\n\n  const inputEl = dialog.querySelector('sl-input')!;\n  const closeButton = dialog.shadowRoot!.querySelector('sl-icon-button')!;\n  const footerButton = dialog.querySelector('sl-button')!;\n\n  expect(activeElementsArray()).to.not.include(inputEl);\n\n  // Sets focus to the input element\n  inputEl.focus();\n\n  expect(activeElementsArray()).to.include(inputEl);\n\n  await sendKeys({ press: tabKey });\n\n  expect(activeElementsArray()).not.to.include(inputEl);\n  expect(activeElementsArray()).to.include(footerButton);\n\n  // Reset focus back to input el\n  inputEl.focus();\n  expect(activeElementsArray()).to.include(inputEl);\n\n  await holdShiftKey(async () => await sendKeys({ press: tabKey }));\n  expect(activeElementsArray()).to.include(closeButton);\n});\n\n// https://github.com/shoelace-style/shoelace/issues/1710\nit('Should respect nested modal instances', async () => {\n  const dialogOne = (): SlDialog => document.querySelector('#dialog-1')!;\n  const dialogTwo = (): SlDialog => document.querySelector('#dialog-2')!;\n\n  // lit-a11y doesn't like the \"autofocus\" attribute.\n  /* eslint-disable */\n  await fixture(html`\n    <div>\n      <sl-button id=\"open-dialog-1\" @click=${() => dialogOne().show()}></sl-button>\n      <sl-dialog id=\"dialog-1\" label=\"Dialog 1\">\n        <sl-button @click=${() => dialogTwo().show()} id=\"open-dialog-2\">Open Dialog 2</sl-button>\n        <sl-button slot=\"footer\" variant=\"primary\">Close</sl-button>\n      </sl-dialog>\n\n      <sl-dialog id=\"dialog-2\" label=\"Dialog 2\">\n        <sl-input id=\"focus-1\" autofocus=\"\" placeholder=\"I will have focus when the dialog is opened\"></sl-input>\n        <sl-input id=\"focus-2\" placeholder=\"Second input\"></sl-input>\n        <sl-button slot=\"footer\" variant=\"primary\" class=\"close-2\">Close</sl-button>\n      </sl-dialog>\n    </div>\n  `);\n  /* eslint-enable */\n\n  const firstFocusedEl = document.querySelector('#focus-1');\n  const secondFocusedEl = document.querySelector('#focus-2');\n\n  // So we can trigger auto-focus stuff\n  await clickOnElement(document.querySelector('#open-dialog-1')!);\n  // These clicks need a ~100ms timeout. I'm assuming for animation reasons?\n  await aTimeout(100);\n  await clickOnElement(document.querySelector('#open-dialog-2')!);\n  await aTimeout(100);\n\n  expect(activeElementsArray()).to.include(firstFocusedEl);\n\n  await sendKeys({ press: tabKey });\n  expect(activeElementsArray()).to.include(secondFocusedEl);\n});\n"
  },
  {
    "path": "src/internal/tabbable.ts",
    "content": "// Cached compute style calls. This is specifically for browsers that dont support `checkVisibility()`.\n// computedStyle calls are \"live\" so they only need to be retrieved once for an element.\nconst computedStyleMap = new WeakMap<Element, CSSStyleDeclaration>();\n\nfunction getCachedComputedStyle(el: HTMLElement): CSSStyleDeclaration {\n  let computedStyle: undefined | CSSStyleDeclaration = computedStyleMap.get(el);\n\n  if (!computedStyle) {\n    computedStyle = window.getComputedStyle(el, null);\n    computedStyleMap.set(el, computedStyle);\n  }\n\n  return computedStyle;\n}\n\nfunction isVisible(el: HTMLElement): boolean {\n  // This is the fastest check, but isn't supported in Safari.\n  if (typeof el.checkVisibility === 'function') {\n    // Opacity is focusable, visibility is not.\n    return el.checkVisibility({ checkOpacity: false, checkVisibilityCSS: true });\n  }\n\n  // Fallback \"polyfill\" for \"checkVisibility\"\n  const computedStyle = getCachedComputedStyle(el);\n\n  return computedStyle.visibility !== 'hidden' && computedStyle.display !== 'none';\n}\n\n// While this behavior isn't standard in Safari / Chrome yet, I think it's the most reasonable\n// way of handling tabbable overflow areas. Browser sniffing seems gross, and it's the most\n// accessible way of handling overflow areas. [Konnor]\nfunction isOverflowingAndTabbable(el: HTMLElement): boolean {\n  const computedStyle = getCachedComputedStyle(el);\n\n  const { overflowY, overflowX } = computedStyle;\n\n  if (overflowY === 'scroll' || overflowX === 'scroll') {\n    return true;\n  }\n\n  if (overflowY !== 'auto' || overflowX !== 'auto') {\n    return false;\n  }\n\n  // Always overflow === \"auto\" by this point\n  const isOverflowingY = el.scrollHeight > el.clientHeight;\n\n  if (isOverflowingY && overflowY === 'auto') {\n    return true;\n  }\n\n  const isOverflowingX = el.scrollWidth > el.clientWidth;\n\n  if (isOverflowingX && overflowX === 'auto') {\n    return true;\n  }\n\n  return false;\n}\n\n/** Determines if the specified element is tabbable using heuristics inspired by https://github.com/focus-trap/tabbable */\nfunction isTabbable(el: HTMLElement) {\n  const tag = el.tagName.toLowerCase();\n\n  const tabindex = Number(el.getAttribute('tabindex'));\n  const hasTabindex = el.hasAttribute('tabindex');\n\n  // elements with a tabindex attribute that is either NaN or <= -1 are not tabbable\n  if (hasTabindex && (isNaN(tabindex) || tabindex <= -1)) {\n    return false;\n  }\n\n  // Elements with a disabled attribute are not tabbable\n  if (el.hasAttribute('disabled')) {\n    return false;\n  }\n\n  // If any parents have \"inert\", we aren't \"tabbable\"\n  if (el.closest('[inert]')) {\n    return false;\n  }\n\n  if (tag === 'input' && el.getAttribute('type') === 'radio') {\n    const rootNode = el.getRootNode() as HTMLElement;\n\n    const findRadios = `input[type='radio'][name=\"${el.getAttribute('name')}\"]`;\n    const firstChecked = rootNode.querySelector(`${findRadios}:checked`);\n\n    if (firstChecked) {\n      return firstChecked === el;\n    }\n\n    const firstRadio = rootNode.querySelector(findRadios);\n\n    return firstRadio === el;\n  }\n\n  if (!isVisible(el)) {\n    return false;\n  }\n\n  // Audio and video elements with the controls attribute are tabbable\n  if ((tag === 'audio' || tag === 'video') && el.hasAttribute('controls')) {\n    return true;\n  }\n\n  // Elements with a tabindex other than -1 are tabbable\n  if (el.hasAttribute('tabindex')) {\n    return true;\n  }\n\n  // Elements with a contenteditable attribute are tabbable\n  if (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false') {\n    return true;\n  }\n\n  // At this point, the following elements are considered tabbable\n  const isNativelyTabbable = [\n    'button',\n    'input',\n    'select',\n    'textarea',\n    'a',\n    'audio',\n    'video',\n    'summary',\n    'iframe'\n  ].includes(tag);\n\n  if (isNativelyTabbable) {\n    return true;\n  }\n\n  // We save the overflow checks for last, because they're the most expensive\n  return isOverflowingAndTabbable(el);\n}\n\n/**\n * Returns the first and last bounding elements that are tabbable. This is more performant than checking every single\n * element because it short-circuits after finding the first and last ones.\n */\nexport function getTabbableBoundary(root: HTMLElement | ShadowRoot) {\n  const tabbableElements = getTabbableElements(root);\n\n  // Find the first and last tabbable elements\n  const start = tabbableElements[0] ?? null;\n  const end = tabbableElements[tabbableElements.length - 1] ?? null;\n\n  return { start, end };\n}\n\n/**\n * This looks funky. Basically a slot's children will always be picked up *if* they're within the `root` element.\n * However, there is an edge case when, if the `root` is wrapped by another shadow DOM, it won't grab the children.\n * This fixes that fun edge case.\n */\nfunction getSlottedChildrenOutsideRootElement(slotElement: HTMLSlotElement, root: HTMLElement | ShadowRoot) {\n  return (slotElement.getRootNode({ composed: true }) as ShadowRoot | null)?.host !== root;\n}\n\nexport function getTabbableElements(root: HTMLElement | ShadowRoot) {\n  const walkedEls = new WeakMap();\n  const tabbableElements: HTMLElement[] = [];\n\n  function walk(el: HTMLElement | ShadowRoot) {\n    if (el instanceof Element) {\n      // if the element has \"inert\" we can just no-op it.\n      if (el.hasAttribute('inert') || el.closest('[inert]')) {\n        return;\n      }\n\n      if (walkedEls.has(el)) {\n        return;\n      }\n      walkedEls.set(el, true);\n\n      if (!tabbableElements.includes(el) && isTabbable(el)) {\n        tabbableElements.push(el);\n      }\n\n      if (el instanceof HTMLSlotElement && getSlottedChildrenOutsideRootElement(el, root)) {\n        el.assignedElements({ flatten: true }).forEach((assignedEl: HTMLElement) => {\n          walk(assignedEl);\n        });\n      }\n\n      if (el.shadowRoot !== null && el.shadowRoot.mode === 'open') {\n        walk(el.shadowRoot);\n      }\n    }\n\n    for (const e of el.children) {\n      walk(e as HTMLElement);\n    }\n  }\n\n  // Collect all elements including the root\n  walk(root);\n\n  // Is this worth having? Most sorts will always add increased overhead. And positive tabindexes shouldn't really be used.\n  // So is it worth being right? Or fast?\n  return tabbableElements.sort((a, b) => {\n    // Make sure we sort by tabindex.\n    const aTabindex = Number(a.getAttribute('tabindex')) || 0;\n    const bTabindex = Number(b.getAttribute('tabindex')) || 0;\n    return bTabindex - aTabindex;\n  });\n}\n"
  },
  {
    "path": "src/internal/test/data-testid-helpers.ts",
    "content": "/**\n * Allows you to find a DOM element based on the value of its `data-testid` attribute.\n * This attribute can be used to decouple identifying dom elements for testing from\n * styling (which is typically done via class selectors) or other ids which serve\n * different purposes.\n * See also https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change\n * Inspired by https://testing-library.com/docs/queries/bytestid/\n * @param {HTMLElement} container - A parent element of the DOM element to find\n * @param {string} testId - The value of the `data-testid` attribute of the component to find.\n * @returns The found element or null if there was no such element\n */\nexport const queryByTestId = <T extends Element>(container: HTMLElement, testId: string): T | null => {\n  return container.querySelector<T>(`[data-testid=\"${testId}\"]`);\n};\n"
  },
  {
    "path": "src/internal/test/element-visible-overflow.ts",
    "content": "/**\n * Given a parent element featuring `overflow: hidden` and a child element inside the parent, this\n * function determines whether the child will be visible taking only the overflow of the parent into account\n * Id does NOT check whether it is hidden or overlapped by another element\n * It basically checks whether the bounding rects of the parent and the child overlap\n *\n * @param {HTMLElement} outerElement - The parent element\n * @param {HTMLElement} innerElement - the child element\n * @returns {Boolean} whether the two elements overlap\n */\nexport const isElementVisibleFromOverflow = (outerElement: Element, innerElement: Element): boolean => {\n  const outerRect = outerElement.getBoundingClientRect();\n  const innerRect = innerElement.getBoundingClientRect();\n  return (\n    outerRect.top <= innerRect.bottom &&\n    innerRect.top <= outerRect.bottom &&\n    outerRect.left <= innerRect.right &&\n    innerRect.left <= outerRect.right\n  );\n};\n"
  },
  {
    "path": "src/internal/test/form-control-base-tests.ts",
    "content": "import { expect, fixture } from '@open-wc/testing';\nimport type { ShoelaceFormControl } from '../shoelace-element.js';\n\ntype CreateControlFn = () => Promise<ShoelaceFormControl>;\n\n/** Runs a set of generic tests for Shoelace form controls */\nexport function runFormControlBaseTests<T extends ShoelaceFormControl = ShoelaceFormControl>(\n  tagNameOrConfig:\n    | string\n    | {\n        tagName: string;\n        init?: (control: T) => void;\n        variantName: string;\n      }\n) {\n  const isStringArg = typeof tagNameOrConfig === 'string';\n  const tagName = isStringArg ? tagNameOrConfig : tagNameOrConfig.tagName;\n\n  // component initialization function or null\n  const init =\n    isStringArg || !tagNameOrConfig.init //\n      ? null\n      : tagNameOrConfig.init || null;\n\n  // either `<tagName>` or `<tagName> (<variantName>)\n  const displayName = isStringArg //\n    ? tagName\n    : `${tagName} (${tagNameOrConfig.variantName})`;\n\n  // creates a testable form control instance\n  const createControl = async () => {\n    const control = await createFormControl<T>(tagName);\n    init?.(control);\n    return control;\n  };\n\n  runAllValidityTests(tagName, displayName, createControl);\n}\n\n//\n// Applicable for all Shoelace form controls. This function checks the behavior of:\n//   - `.validity`\n//   - `.validationMessage`,\n//   - `.checkValidity()`\n//   - `.reportValidity()`\n//   - `.setCustomValidity(msg)`\n//   - `.getForm()`\n//\nfunction runAllValidityTests(\n  tagName: string, //\n  displayName: string,\n  createControl: () => Promise<ShoelaceFormControl>\n) {\n  // will be used later to retrieve meta information about the control\n  describe(`Form validity base test for ${displayName}`, async () => {\n    it('should have a property `validity` of type `object`', async () => {\n      const control = await createControl();\n      expect(control).satisfy(() => control.validity !== null && typeof control.validity === 'object');\n    });\n\n    it('should have a property `validationMessage` of type `string`', async () => {\n      const control = await createControl();\n      expect(control).satisfy(() => typeof control.validationMessage === 'string');\n    });\n\n    it('should implement method `checkValidity`', async () => {\n      const control = await createControl();\n      expect(control).satisfies(() => typeof control.checkValidity === 'function');\n    });\n\n    it('should implement method `setCustomValidity`', async () => {\n      const control = await createControl();\n      expect(control).satisfies(() => typeof control.setCustomValidity === 'function');\n    });\n\n    it('should implement method `reportValidity`', async () => {\n      const control = await createControl();\n      expect(control).satisfies(() => typeof control.reportValidity === 'function');\n    });\n\n    it('should be valid initially', async () => {\n      const control = await createControl();\n      expect(control.validity.valid).to.equal(true);\n    });\n\n    it('should make sure that calling `.checkValidity()` will return `true` when valid', async () => {\n      const control = await createControl();\n      expect(control.checkValidity()).to.equal(true);\n    });\n\n    it('should make sure that calling `.reportValidity()` will return `true` when valid', async () => {\n      const control = await createControl();\n      expect(control.reportValidity()).to.equal(true);\n    });\n\n    it('should not emit an `sl-invalid` event when `.checkValidity()` is called while valid', async () => {\n      const control = await createControl();\n      const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.checkValidity());\n      expect(emittedEvents.length).to.equal(0);\n    });\n\n    it('should not emit an `sl-invalid` event when `.reportValidity()` is called while valid', async () => {\n      const control = await createControl();\n      const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.reportValidity());\n      expect(emittedEvents.length).to.equal(0);\n    });\n\n    // TODO: As soon as `SlRadioGroup` has a property `disabled` this\n    // condition can be removed\n    if (tagName !== 'sl-radio-group') {\n      it('should not emit an `sl-invalid` event when `.checkValidity()` is called in custom error case while disabled', async () => {\n        const control = await createControl();\n        control.setCustomValidity('error');\n        control.disabled = true;\n        await control.updateComplete;\n        const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.checkValidity());\n        expect(emittedEvents.length).to.equal(0);\n      });\n\n      it('should not emit an `sl-invalid` event when `.reportValidity()` is called in custom error case while disabled', async () => {\n        const control = await createControl();\n        control.setCustomValidity('error');\n        control.disabled = true;\n        await control.updateComplete;\n        const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.reportValidity());\n        expect(emittedEvents.length).to.equal(0);\n      });\n\n      it('Should find the correct form when given a form property', async () => {\n        const formId = 'test-form';\n        const form = await fixture(`<form id='${formId}'></form>`);\n        const control = await createControl();\n        expect(control.getForm()).to.equal(null);\n        control.form = 'test-form';\n        await control.updateComplete;\n        expect(control.getForm()).to.equal(form);\n      });\n\n      it('Should find the correct form when given a form attribute', async () => {\n        const formId = 'test-form';\n        const form = await fixture(`<form id='${formId}'></form>`);\n        const control = await createControl();\n        expect(control.getForm()).to.equal(null);\n        control.setAttribute('form', 'test-form');\n\n        await control.updateComplete;\n        expect(control.getForm()).to.equal(form);\n      });\n    }\n\n    // Run special tests depending on component type\n\n    const mode = getMode(await createControl());\n\n    if (mode === 'slButtonOfTypeButton') {\n      runSpecialTests_slButtonOfTypeButton(createControl);\n    } else if (mode === 'slButtonWithHRef') {\n      runSpecialTests_slButtonWithHref(createControl);\n    } else {\n      runSpecialTests_standard(createControl);\n    }\n  });\n}\n\n//\n//  Special tests for <sl-button type=\"button\">\n//\nfunction runSpecialTests_slButtonOfTypeButton(createControl: CreateControlFn) {\n  it('should make sure that `.validity.valid` is `false` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.validity.valid).to.equal(false);\n  });\n\n  it('should make sure that calling `.checkValidity()` will still return `true` when custom error has been set', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.checkValidity()).to.equal(true);\n  });\n\n  it('should make sure that calling `.reportValidity()` will still return `true` when custom error has been set', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.reportValidity()).to.equal(true);\n  });\n\n  it('should not emit an `sl-invalid` event when `.checkValidity()` is called in custom error case, and not disabled', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    control.disabled = false;\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.checkValidity());\n    expect(emittedEvents.length).to.equal(0);\n  });\n\n  it('should not emit an `sl-invalid` event when `.reportValidity()` is called in custom error case, and not disabled', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    control.disabled = false;\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.reportValidity());\n\n    expect(emittedEvents.length).to.equal(0);\n  });\n}\n\n//\n// Special tests for <sl-button href=\"...\">\n//\nfunction runSpecialTests_slButtonWithHref(createControl: CreateControlFn) {\n  it('should make sure that calling `.checkValidity()` will return `true` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.checkValidity()).to.equal(true);\n  });\n\n  it('should make sure that calling `.reportValidity()` will return `true` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.reportValidity()).to.equal(true);\n  });\n\n  it('should not emit an `sl-invalid` event when `.checkValidity()` is called in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.checkValidity());\n    expect(emittedEvents.length).to.equal(0);\n  });\n\n  it('should not emit an `sl-invalid` event when `.reportValidity()` is called in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.reportValidity());\n    expect(emittedEvents.length).to.equal(0);\n  });\n}\n\n//\n// Special tests for all components with standard behavior\n//\nfunction runSpecialTests_standard(createControl: CreateControlFn) {\n  it('should make sure that `.validity.valid` is `false` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.validity.valid).to.equal(false);\n  });\n\n  it('should make sure that calling `.checkValidity()` will return `false` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.checkValidity()).to.equal(false);\n  });\n\n  it('should make sure that calling `.reportValidity()` will return `false` in custom error case', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    expect(control.reportValidity()).to.equal(false);\n  });\n\n  it('should emit an `sl-invalid` event when `.checkValidity()` is called in custom error case and not disabled', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    control.disabled = false;\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.checkValidity());\n    expect(emittedEvents.length).to.equal(1);\n  });\n\n  it('should emit an `sl-invalid` event when `.reportValidity()` is called in custom error case and not disabled', async () => {\n    const control = await createControl();\n    control.setCustomValidity('error');\n    control.disabled = false;\n    await control.updateComplete;\n    const emittedEvents = checkEventEmissions(control, 'sl-invalid', () => control.reportValidity());\n    expect(emittedEvents.length).to.equal(1);\n  });\n}\n\n//\n// Local helper functions\n//\n\n// Creates a testable Shoelace form control instance\nasync function createFormControl<T extends ShoelaceFormControl = ShoelaceFormControl>(tagName: string): Promise<T> {\n  return await fixture<T>(`<${tagName}></${tagName}>`);\n}\n\n// Runs an action while listening for emitted events of a given type. Returns an array of all events of the given type\n// that have been been emitted while the action was running.\nfunction checkEventEmissions(control: ShoelaceFormControl, eventType: string, action: () => void): Event[] {\n  const emittedEvents: Event[] = [];\n\n  const eventHandler = (event: Event) => {\n    emittedEvents.push(event);\n  };\n\n  try {\n    control.addEventListener(eventType, eventHandler);\n    action();\n  } finally {\n    control.removeEventListener(eventType, eventHandler);\n  }\n\n  return emittedEvents;\n}\n\n// Component `sl-button` behaves quite different to the other components. To keep things simple we use simple conditions\n// here. `sl-button` might stay the only component in Shoelace core behaves that way, so we just hard code it here.\nfunction getMode(control: ShoelaceFormControl) {\n  if (\n    control.localName === 'sl-button' && //\n    'href' in control &&\n    'type' in control &&\n    control.type === 'button' &&\n    !control.href\n  ) {\n    return 'slButtonOfTypeButton';\n  }\n\n  // <sl-button href=\"...\">\n  if (control.localName === 'sl-button' && 'href' in control && !!control.href) {\n    return 'slButtonWithHRef';\n  }\n\n  // all other components\n  return 'standard';\n}\n"
  },
  {
    "path": "src/internal/test/wait-for-scrolling.ts",
    "content": "/**\n * Wait until an element has stopped scrolling\n * This considers the element to have stopped scrolling, as soon as it did not change its\n * scroll position for 20 successive animation frames\n * @param {HTMLElement} element - The element which is scrolled\n * @param {numeric} timeoutInMs - A timeout in ms. If the timeout has elapsed, the promise rejects\n * @returns A promise which resolves after the scrolling has stopped\n */\nexport const waitForScrollingToEnd = (element: Element, timeoutInMs = 500): Promise<void> => {\n  let lastLeft = element.scrollLeft;\n  let lastTop = element.scrollTop;\n  let framesWithoutChange = 0;\n\n  return new Promise((resolve, reject) => {\n    const timeout = window.setTimeout(() => {\n      reject(new Error('Waiting for scroll end timed out'));\n    }, timeoutInMs);\n\n    function checkScrollingChanged() {\n      if (element.scrollLeft !== lastLeft || element.scrollTop !== lastTop) {\n        framesWithoutChange = 0;\n        lastLeft = window.scrollX;\n        lastTop = window.scrollY;\n      } else {\n        framesWithoutChange++;\n        if (framesWithoutChange >= 20) {\n          clearTimeout(timeout);\n          resolve();\n        }\n      }\n      window.requestAnimationFrame(checkScrollingChanged);\n    }\n\n    checkScrollingChanged();\n  });\n};\n"
  },
  {
    "path": "src/internal/test.ts",
    "content": "import { sendMouse } from '@web/test-runner-commands';\n\nfunction determineMousePosition(el: Element, position: string, offsetX: number, offsetY: number) {\n  const { x, y, width, height } = el.getBoundingClientRect();\n  const centerX = Math.floor(x + window.scrollX + width / 2);\n  const centerY = Math.floor(y + window.scrollY + height / 2);\n  let clickX: number;\n  let clickY: number;\n\n  switch (position) {\n    case 'top':\n      clickX = centerX;\n      clickY = y;\n      break;\n    case 'right':\n      clickX = x + width - 1;\n      clickY = centerY;\n      break;\n    case 'bottom':\n      clickX = centerX;\n      clickY = y + height - 1;\n      break;\n    case 'left':\n      clickX = x;\n      clickY = centerY;\n      break;\n    default:\n      clickX = centerX;\n      clickY = centerY;\n  }\n\n  clickX += offsetX;\n  clickY += offsetY;\n  return { clickX, clickY };\n}\n\n/** A testing utility that measures an element's position and clicks on it. */\nexport async function clickOnElement(\n  /** The element to click */\n  el: Element,\n  /** The location of the element to click */\n  position: 'top' | 'right' | 'bottom' | 'left' | 'center' = 'center',\n  /** The horizontal offset to apply to the position when clicking */\n  offsetX = 0,\n  /** The vertical offset to apply to the position when clicking */\n  offsetY = 0\n) {\n  const { clickX, clickY } = determineMousePosition(el, position, offsetX, offsetY);\n\n  await sendMouse({ type: 'click', position: [Math.round(clickX), Math.round(clickY)] });\n}\n\n/** A testing utility that moves the mouse onto an element. */\nexport async function moveMouseOnElement(\n  /** The element to click */\n  el: Element,\n  /** The location of the element to click */\n  position: 'top' | 'right' | 'bottom' | 'left' | 'center' = 'center',\n  /** The horizontal offset to apply to the position when clicking */\n  offsetX = 0,\n  /** The vertical offset to apply to the position when clicking */\n  offsetY = 0\n) {\n  const { clickX, clickY } = determineMousePosition(el, position, offsetX, offsetY);\n\n  await sendMouse({ type: 'move', position: [clickX, clickY] });\n}\n\n/** A testing utility that drags an element with the mouse. */\nexport async function dragElement(\n  /** The element to drag */\n  el: Element,\n  /** The horizontal distance to drag in pixels */\n  deltaX = 0,\n  /** The vertical distance to drag in pixels */\n  deltaY = 0,\n  callbacks: {\n    afterMouseDown?: () => void | Promise<void>;\n    afterMouseMove?: () => void | Promise<void>;\n  } = {}\n): Promise<void> {\n  await moveMouseOnElement(el);\n  await sendMouse({ type: 'down' });\n\n  await callbacks.afterMouseDown?.();\n\n  const { clickX, clickY } = determineMousePosition(el, 'center', deltaX, deltaY);\n  await sendMouse({ type: 'move', position: [clickX, clickY] });\n\n  await callbacks.afterMouseMove?.();\n\n  await sendMouse({ type: 'up' });\n}\n"
  },
  {
    "path": "src/internal/watch.ts",
    "content": "import type { LitElement } from 'lit';\n\ntype UpdateHandler = (prev?: unknown, next?: unknown) => void;\n\ntype NonUndefined<A> = A extends undefined ? never : A;\n\ntype UpdateHandlerFunctionKeys<T extends object> = {\n  [K in keyof T]-?: NonUndefined<T[K]> extends UpdateHandler ? K : never;\n}[keyof T];\n\ninterface WatchOptions {\n  /**\n   * If true, will only start watching after the initial update/render\n   */\n  waitUntilFirstUpdate?: boolean;\n}\n\n/**\n * Runs when observed properties change, e.g. @property or @state, but before the component updates. To wait for an\n * update to complete after a change occurs, use `await this.updateComplete` in the handler. To start watching after the\n * initial update/render, use `{ waitUntilFirstUpdate: true }` or `this.hasUpdated` in the handler.\n *\n * Usage:\n *\n * @watch('propName')\n * handlePropChange(oldValue, newValue) {\n *   ...\n * }\n */\nexport function watch(propertyName: string | string[], options?: WatchOptions) {\n  const resolvedOptions: Required<WatchOptions> = {\n    waitUntilFirstUpdate: false,\n    ...options\n  };\n  return <ElemClass extends LitElement>(proto: ElemClass, decoratedFnName: UpdateHandlerFunctionKeys<ElemClass>) => {\n    // @ts-expect-error - update is a protected property\n    const { update } = proto;\n    const watchedProperties = Array.isArray(propertyName) ? propertyName : [propertyName];\n\n    // @ts-expect-error - update is a protected property\n    proto.update = function (this: ElemClass, changedProps: Map<keyof ElemClass, ElemClass[keyof ElemClass]>) {\n      watchedProperties.forEach(property => {\n        const key = property as keyof ElemClass;\n        if (changedProps.has(key)) {\n          const oldValue = changedProps.get(key);\n          const newValue = this[key];\n\n          if (oldValue !== newValue) {\n            if (!resolvedOptions.waitUntilFirstUpdate || this.hasUpdated) {\n              (this[decoratedFnName] as unknown as UpdateHandler)(oldValue, newValue);\n            }\n          }\n        }\n      });\n\n      update.call(this, changedProps);\n    };\n  };\n}\n"
  },
  {
    "path": "src/shoelace-autoloader.ts",
    "content": "import { getBasePath } from './utilities/base-path.js';\n\nconst observer = new MutationObserver(mutations => {\n  for (const { addedNodes } of mutations) {\n    for (const node of addedNodes) {\n      if (node.nodeType === Node.ELEMENT_NODE) {\n        discover(node as Element);\n      }\n    }\n  }\n});\n\n/**\n * Checks a node for undefined elements and attempts to register them.\n */\nexport async function discover(root: Element | ShadowRoot) {\n  const rootTagName = root instanceof Element ? root.tagName.toLowerCase() : '';\n  const rootIsShoelaceElement = rootTagName?.startsWith('sl-');\n  const tags = [...root.querySelectorAll(':not(:defined)')]\n    .map(el => el.tagName.toLowerCase())\n    .filter(tag => tag.startsWith('sl-'));\n\n  // If the root element is an undefined Shoelace component, add it to the list\n  if (rootIsShoelaceElement && !customElements.get(rootTagName)) {\n    tags.push(rootTagName);\n  }\n\n  // Make the list unique\n  const tagsToRegister = [...new Set(tags)];\n\n  await Promise.allSettled(tagsToRegister.map(tagName => register(tagName)));\n}\n\n/**\n * Registers an element by tag name.\n */\nfunction register(tagName: string): Promise<void> {\n  // If the element is already defined, there's nothing more to do\n  if (customElements.get(tagName)) {\n    return Promise.resolve();\n  }\n\n  const tagWithoutPrefix = tagName.replace(/^sl-/i, '');\n  const path = getBasePath(`components/${tagWithoutPrefix}/${tagWithoutPrefix}.js`);\n\n  // Register it\n  return new Promise((resolve, reject) => {\n    import(path).then(() => resolve()).catch(() => reject(new Error(`Unable to autoload <${tagName}> from ${path}`)));\n  });\n}\n\n// Initial discovery\ndiscover(document.body);\n\n// Listen for new undefined elements\nobserver.observe(document.documentElement, { subtree: true, childList: true });\n"
  },
  {
    "path": "src/shoelace.ts",
    "content": "// Components\nexport { default as SlAlert } from './components/alert/alert.js';\nexport { default as SlAnimatedImage } from './components/animated-image/animated-image.js';\nexport { default as SlAnimation } from './components/animation/animation.js';\nexport { default as SlAvatar } from './components/avatar/avatar.js';\nexport { default as SlBadge } from './components/badge/badge.js';\nexport { default as SlBreadcrumb } from './components/breadcrumb/breadcrumb.js';\nexport { default as SlBreadcrumbItem } from './components/breadcrumb-item/breadcrumb-item.js';\nexport { default as SlButton } from './components/button/button.js';\nexport { default as SlButtonGroup } from './components/button-group/button-group.js';\nexport { default as SlCard } from './components/card/card.js';\nexport { default as SlCarousel } from './components/carousel/carousel.js';\nexport { default as SlCarouselItem } from './components/carousel-item/carousel-item.js';\nexport { default as SlCheckbox } from './components/checkbox/checkbox.js';\nexport { default as SlColorPicker } from './components/color-picker/color-picker.js';\nexport { default as SlCopyButton } from './components/copy-button/copy-button.js';\nexport { default as SlDetails } from './components/details/details.js';\nexport { default as SlDialog } from './components/dialog/dialog.js';\nexport { default as SlDivider } from './components/divider/divider.js';\nexport { default as SlDrawer } from './components/drawer/drawer.js';\nexport { default as SlDropdown } from './components/dropdown/dropdown.js';\nexport { default as SlFormatBytes } from './components/format-bytes/format-bytes.js';\nexport { default as SlFormatDate } from './components/format-date/format-date.js';\nexport { default as SlFormatNumber } from './components/format-number/format-number.js';\nexport { default as SlIcon } from './components/icon/icon.js';\nexport { default as SlIconButton } from './components/icon-button/icon-button.js';\nexport { default as SlImageComparer } from './components/image-comparer/image-comparer.js';\nexport { default as SlInclude } from './components/include/include.js';\nexport { default as SlInput } from './components/input/input.js';\nexport { default as SlMenu } from './components/menu/menu.js';\nexport { default as SlMenuItem } from './components/menu-item/menu-item.js';\nexport { default as SlMenuLabel } from './components/menu-label/menu-label.js';\nexport { default as SlMutationObserver } from './components/mutation-observer/mutation-observer.js';\nexport { default as SlOption } from './components/option/option.js';\nexport { default as SlPopup } from './components/popup/popup.js';\nexport { default as SlProgressBar } from './components/progress-bar/progress-bar.js';\nexport { default as SlProgressRing } from './components/progress-ring/progress-ring.js';\nexport { default as SlQrCode } from './components/qr-code/qr-code.js';\nexport { default as SlRadio } from './components/radio/radio.js';\nexport { default as SlRadioButton } from './components/radio-button/radio-button.js';\nexport { default as SlRadioGroup } from './components/radio-group/radio-group.js';\nexport { default as SlRange } from './components/range/range.js';\nexport { default as SlRating } from './components/rating/rating.js';\nexport { default as SlRelativeTime } from './components/relative-time/relative-time.js';\nexport { default as SlResizeObserver } from './components/resize-observer/resize-observer.js';\nexport { default as SlSelect } from './components/select/select.js';\nexport { default as SlSkeleton } from './components/skeleton/skeleton.js';\nexport { default as SlSpinner } from './components/spinner/spinner.js';\nexport { default as SlSplitPanel } from './components/split-panel/split-panel.js';\nexport { default as SlSwitch } from './components/switch/switch.js';\nexport { default as SlTab } from './components/tab/tab.js';\nexport { default as SlTabGroup } from './components/tab-group/tab-group.js';\nexport { default as SlTabPanel } from './components/tab-panel/tab-panel.js';\nexport { default as SlTag } from './components/tag/tag.js';\nexport { default as SlTextarea } from './components/textarea/textarea.js';\nexport { default as SlTooltip } from './components/tooltip/tooltip.js';\nexport { default as SlTree } from './components/tree/tree.js';\nexport { default as SlTreeItem } from './components/tree-item/tree-item.js';\nexport { default as SlVisuallyHidden } from './components/visually-hidden/visually-hidden.js';\n/* plop:component */\n\n// Utilities\nexport * from './utilities/animation.js';\nexport * from './utilities/base-path.js';\nexport * from './utilities/icon-library.js';\nexport * from './utilities/form.js';\n\n// Events\nexport * from './events/events.js';\n"
  },
  {
    "path": "src/styles/component.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  :host {\n    box-sizing: border-box;\n  }\n\n  :host *,\n  :host *::before,\n  :host *::after {\n    box-sizing: inherit;\n  }\n\n  [hidden] {\n    display: none !important;\n  }\n`;\n"
  },
  {
    "path": "src/styles/form-control.styles.ts",
    "content": "import { css } from 'lit';\n\nexport default css`\n  .form-control .form-control__label {\n    display: none;\n  }\n\n  .form-control .form-control__help-text {\n    display: none;\n  }\n\n  /* Label */\n  .form-control--has-label .form-control__label {\n    display: inline-block;\n    color: var(--sl-input-label-color);\n    margin-bottom: var(--sl-spacing-3x-small);\n  }\n\n  .form-control--has-label.form-control--small .form-control__label {\n    font-size: var(--sl-input-label-font-size-small);\n  }\n\n  .form-control--has-label.form-control--medium .form-control__label {\n    font-size: var(--sl-input-label-font-size-medium);\n  }\n\n  .form-control--has-label.form-control--large .form-control__label {\n    font-size: var(--sl-input-label-font-size-large);\n  }\n\n  :host([required]) .form-control--has-label .form-control__label::after {\n    content: var(--sl-input-required-content);\n    margin-inline-start: var(--sl-input-required-content-offset);\n    color: var(--sl-input-required-content-color);\n  }\n\n  /* Help text */\n  .form-control--has-help-text .form-control__help-text {\n    display: block;\n    color: var(--sl-input-help-text-color);\n    margin-top: var(--sl-spacing-3x-small);\n  }\n\n  .form-control--has-help-text.form-control--small .form-control__help-text {\n    font-size: var(--sl-input-help-text-font-size-small);\n  }\n\n  .form-control--has-help-text.form-control--medium .form-control__help-text {\n    font-size: var(--sl-input-help-text-font-size-medium);\n  }\n\n  .form-control--has-help-text.form-control--large .form-control__help-text {\n    font-size: var(--sl-input-help-text-font-size-large);\n  }\n\n  .form-control--has-help-text.form-control--radio-group .form-control__help-text {\n    margin-top: var(--sl-spacing-2x-small);\n  }\n`;\n"
  },
  {
    "path": "src/themes/_utility.css",
    "content": "/*\n * This file contains utility classes that can't be contained in a component and must be applied to the light DOM. None\n * of the rules in this stylesheet should target component tags or HTML tags, and all classes *must* start with \".sl-\"\n * to reduce the possibility of collisions.\n */\n\n@supports (scrollbar-gutter: stable) {\n  .sl-scroll-lock {\n    scrollbar-gutter: var(--sl-scroll-lock-gutter) !important;\n  }\n\n  .sl-scroll-lock body {\n    overflow: hidden !important;\n  }\n}\n\n/** This can go away once Safari has scrollbar-gutter support. */\n@supports not (scrollbar-gutter: stable) {\n  .sl-scroll-lock body {\n    padding-right: var(--sl-scroll-lock-size) !important;\n    overflow: hidden !important;\n  }\n}\n\n.sl-toast-stack {\n  position: fixed;\n  top: 0;\n  inset-inline-end: 0;\n  z-index: var(--sl-z-index-toast);\n  width: 28rem;\n  max-width: 100%;\n  max-height: 100%;\n  overflow: auto;\n}\n\n.sl-toast-stack sl-alert {\n  margin: var(--sl-spacing-medium);\n}\n\n.sl-toast-stack sl-alert::part(base) {\n  box-shadow: var(--sl-shadow-large);\n}\n"
  },
  {
    "path": "src/themes/dark.css",
    "content": ":host,\n.sl-theme-dark {\n  color-scheme: dark;\n\n  /*\n   * Color Primitives\n   */\n\n  /* Gray */\n  --sl-color-gray-50: hsl(240 5.1% 15%);\n  --sl-color-gray-100: hsl(240 5.7% 18.2%);\n  --sl-color-gray-200: hsl(240 4.6% 22%);\n  --sl-color-gray-300: hsl(240 5% 27.6%);\n  --sl-color-gray-400: hsl(240 5% 35.5%);\n  --sl-color-gray-500: hsl(240 3.7% 44%);\n  --sl-color-gray-600: hsl(240 5.3% 58%);\n  --sl-color-gray-700: hsl(240 5.6% 73%);\n  --sl-color-gray-800: hsl(240 7.3% 84%);\n  --sl-color-gray-900: hsl(240 9.1% 91.8%);\n  --sl-color-gray-950: hsl(0 0% 95%);\n\n  /* Red */\n  --sl-color-red-50: hsl(0 56% 23.9%);\n  --sl-color-red-100: hsl(0.6 60% 33.9%);\n  --sl-color-red-200: hsl(0.9 67.2% 37.1%);\n  --sl-color-red-300: hsl(1.1 71.3% 43.7%);\n  --sl-color-red-400: hsl(1 76% 52.5%);\n  --sl-color-red-500: hsl(0.7 89.6% 57.2%);\n  --sl-color-red-600: hsl(0 98.6% 67.9%);\n  --sl-color-red-700: hsl(0 100% 72.3%);\n  --sl-color-red-800: hsl(0 100% 85.6%);\n  --sl-color-red-900: hsl(0 100% 90.3%);\n  --sl-color-red-950: hsl(0 100% 95.9%);\n\n  /* Orange */\n  --sl-color-orange-50: hsl(15 64.2% 23.3%);\n  --sl-color-orange-100: hsl(15.1 70.9% 31.1%);\n  --sl-color-orange-200: hsl(15.3 75.7% 35.5%);\n  --sl-color-orange-300: hsl(17.1 83.5% 42.7%);\n  --sl-color-orange-400: hsl(20.1 88% 50.8%);\n  --sl-color-orange-500: hsl(24.3 100% 50.5%);\n  --sl-color-orange-600: hsl(27.2 100% 57.7%);\n  --sl-color-orange-700: hsl(31.3 100% 68.7%);\n  --sl-color-orange-800: hsl(33.8 100% 79.3%);\n  --sl-color-orange-900: hsl(38.9 100% 87.7%);\n  --sl-color-orange-950: hsl(46.2 100% 95%);\n\n  /* Amber */\n  --sl-color-amber-50: hsl(21.9 66.3% 21.1%);\n  --sl-color-amber-100: hsl(21.5 73.6% 29.7%);\n  --sl-color-amber-200: hsl(22.3 77.6% 33.3%);\n  --sl-color-amber-300: hsl(25.4 84.2% 39.6%);\n  --sl-color-amber-400: hsl(31.4 87.4% 46.7%);\n  --sl-color-amber-500: hsl(37 96.6% 48.3%);\n  --sl-color-amber-600: hsl(43.3 100% 53.4%);\n  --sl-color-amber-700: hsl(46.5 100% 61.1%);\n  --sl-color-amber-800: hsl(49.3 100% 73%);\n  --sl-color-amber-900: hsl(51.8 100% 85%);\n  --sl-color-amber-950: hsl(60 100% 94.6%);\n\n  /* Yellow */\n  --sl-color-yellow-50: hsl(32.5 60% 18.2%);\n  --sl-color-yellow-100: hsl(28.1 68.6% 29%);\n  --sl-color-yellow-200: hsl(31.3 75.8% 30.8%);\n  --sl-color-yellow-300: hsl(34.7 84.4% 35.3%);\n  --sl-color-yellow-400: hsl(40.1 87.3% 43.3%);\n  --sl-color-yellow-500: hsl(44.7 88% 46%);\n  --sl-color-yellow-600: hsl(47.7 100% 50.9%);\n  --sl-color-yellow-700: hsl(51.3 100% 59.9%);\n  --sl-color-yellow-800: hsl(54.6 100% 73%);\n  --sl-color-yellow-900: hsl(58.9 100% 84.2%);\n  --sl-color-yellow-950: hsl(60 100% 94%);\n\n  /* Lime */\n  --sl-color-lime-50: hsl(86.5 54.4% 18%);\n  --sl-color-lime-100: hsl(87.6 56.8% 23.3%);\n  --sl-color-lime-200: hsl(85.8 63.2% 24.5%);\n  --sl-color-lime-300: hsl(86.1 72% 29.4%);\n  --sl-color-lime-400: hsl(85.5 76.8% 37.3%);\n  --sl-color-lime-500: hsl(84.3 74.2% 42.1%);\n  --sl-color-lime-600: hsl(82.8 81.5% 52.6%);\n  --sl-color-lime-700: hsl(82 89.9% 64%);\n  --sl-color-lime-800: hsl(80.9 97.9% 76.6%);\n  --sl-color-lime-900: hsl(77.9 100% 85.8%);\n  --sl-color-lime-950: hsl(69.5 100% 93.8%);\n\n  /* Green */\n  --sl-color-green-50: hsl(144.3 53.6% 16%);\n  --sl-color-green-100: hsl(143.2 55.4% 23.5%);\n  --sl-color-green-200: hsl(141.5 58.2% 26.3%);\n  --sl-color-green-300: hsl(140.8 64.2% 31.8%);\n  --sl-color-green-400: hsl(140.3 68% 39.2%);\n  --sl-color-green-500: hsl(141.1 64.9% 43%);\n  --sl-color-green-600: hsl(141.6 72.4% 55.2%);\n  --sl-color-green-700: hsl(141.7 82.7% 70.1%);\n  --sl-color-green-800: hsl(141 90.9% 82.1%);\n  --sl-color-green-900: hsl(142 100% 89.1%);\n  --sl-color-green-950: hsl(144 100% 95.5%);\n\n  /* Emerald */\n  --sl-color-emerald-50: hsl(164.3 75% 13.5%);\n  --sl-color-emerald-100: hsl(163.5 72.6% 20.1%);\n  --sl-color-emerald-200: hsl(162.1 73.7% 22.4%);\n  --sl-color-emerald-300: hsl(161.3 77.3% 27.6%);\n  --sl-color-emerald-400: hsl(159.6 77.1% 34.3%);\n  --sl-color-emerald-500: hsl(159.1 73.5% 37.9%);\n  --sl-color-emerald-600: hsl(157.8 66.8% 48.9%);\n  --sl-color-emerald-700: hsl(156.2 76.1% 63.8%);\n  --sl-color-emerald-800: hsl(152.4 84.4% 77.4%);\n  --sl-color-emerald-900: hsl(149.3 100% 87%);\n  --sl-color-emerald-950: hsl(158.6 100% 94.8%);\n\n  /* Teal */\n  --sl-color-teal-50: hsl(176.5 51.5% 15.4%);\n  --sl-color-teal-100: hsl(175.9 54.7% 22.3%);\n  --sl-color-teal-200: hsl(175.9 60.7% 23.9%);\n  --sl-color-teal-300: hsl(174.5 67.3% 28.8%);\n  --sl-color-teal-400: hsl(174.4 71.9% 34.9%);\n  --sl-color-teal-500: hsl(173.1 71% 38.3%);\n  --sl-color-teal-600: hsl(172.3 68.2% 48.1%);\n  --sl-color-teal-700: hsl(170.5 81.3% 61.5%);\n  --sl-color-teal-800: hsl(168.4 92.1% 75.2%);\n  --sl-color-teal-900: hsl(168.3 100% 86%);\n  --sl-color-teal-950: hsl(180 100% 95.5%);\n\n  /* Cyan */\n  --sl-color-cyan-50: hsl(197.1 53.8% 20.3%);\n  --sl-color-cyan-100: hsl(196.8 57.3% 27.2%);\n  --sl-color-cyan-200: hsl(195.3 62.7% 29.4%);\n  --sl-color-cyan-300: hsl(193.5 71.3% 34.1%);\n  --sl-color-cyan-400: hsl(192.5 76.8% 40.6%);\n  --sl-color-cyan-500: hsl(189.4 78.6% 42.6%);\n  --sl-color-cyan-600: hsl(188.2 89.1% 51.7%);\n  --sl-color-cyan-700: hsl(187 98.6% 66.2%);\n  --sl-color-cyan-800: hsl(184.9 100% 78.3%);\n  --sl-color-cyan-900: hsl(180 100% 86.6%);\n  --sl-color-cyan-950: hsl(180 100% 94.8%);\n\n  /* Sky */\n  --sl-color-sky-50: hsl(203 63.8% 20.9%);\n  --sl-color-sky-100: hsl(203.4 70.4% 28%);\n  --sl-color-sky-200: hsl(202.7 75.8% 30.8%);\n  --sl-color-sky-300: hsl(203.1 80.4% 36.1%);\n  --sl-color-sky-400: hsl(202.1 80.5% 44.3%);\n  --sl-color-sky-500: hsl(199.7 85.9% 47.7%);\n  --sl-color-sky-600: hsl(198.7 97.9% 57.2%);\n  --sl-color-sky-700: hsl(198.7 100% 70.5%);\n  --sl-color-sky-800: hsl(198.8 100% 82.5%);\n  --sl-color-sky-900: hsl(198.5 100% 89.9%);\n  --sl-color-sky-950: hsl(186 100% 95.5%);\n\n  /* Blue */\n  --sl-color-blue-50: hsl(227.1 49.5% 22.7%);\n  --sl-color-blue-100: hsl(225.8 58.9% 36.8%);\n  --sl-color-blue-200: hsl(227.7 64.4% 42.9%);\n  --sl-color-blue-300: hsl(226.1 72.7% 51.2%);\n  --sl-color-blue-400: hsl(222.6 86.5% 56.3%);\n  --sl-color-blue-500: hsl(217.8 95.8% 57.4%);\n  --sl-color-blue-600: hsl(213.3 100% 65%);\n  --sl-color-blue-700: hsl(210.9 100% 74.8%);\n  --sl-color-blue-800: hsl(211.5 100% 83.4%);\n  --sl-color-blue-900: hsl(211 100% 88.9%);\n  --sl-color-blue-950: hsl(201.8 100% 95.3%);\n\n  /* Indigo */\n  --sl-color-indigo-50: hsl(243.5 40.8% 27%);\n  --sl-color-indigo-100: hsl(242.9 45.7% 37.6%);\n  --sl-color-indigo-200: hsl(244.7 52.7% 43.1%);\n  --sl-color-indigo-300: hsl(245.3 60.5% 52.4%);\n  --sl-color-indigo-400: hsl(244.1 79.2% 60.4%);\n  --sl-color-indigo-500: hsl(239.6 88.7% 63.8%);\n  --sl-color-indigo-600: hsl(234.5 96.7% 70.9%);\n  --sl-color-indigo-700: hsl(229.4 100% 78.3%);\n  --sl-color-indigo-800: hsl(227.1 100% 85%);\n  --sl-color-indigo-900: hsl(223.8 100% 89.9%);\n  --sl-color-indigo-950: hsl(220 100% 95.1%);\n\n  /* Violet */\n  --sl-color-violet-50: hsl(265.1 57.3% 25.4%);\n  --sl-color-violet-100: hsl(263.5 63.8% 39.4%);\n  --sl-color-violet-200: hsl(263.4 66.2% 44.1%);\n  --sl-color-violet-300: hsl(263.7 72.8% 52.4%);\n  --sl-color-violet-400: hsl(262.5 87.3% 59.8%);\n  --sl-color-violet-500: hsl(258.3 95.1% 63.2%);\n  --sl-color-violet-600: hsl(255.1 100% 67.2%);\n  --sl-color-violet-700: hsl(253 100% 81.5%);\n  --sl-color-violet-800: hsl(251.7 100% 87.9%);\n  --sl-color-violet-900: hsl(254.1 100% 91.7%);\n  --sl-color-violet-950: hsl(257.1 100% 96.1%);\n\n  /* Purple */\n  --sl-color-purple-50: hsl(276 54.3% 20.5%);\n  --sl-color-purple-100: hsl(273.6 61.8% 35.4%);\n  --sl-color-purple-200: hsl(272.9 64% 41.4%);\n  --sl-color-purple-300: hsl(271.9 68.1% 49.2%);\n  --sl-color-purple-400: hsl(271.5 85.1% 57.8%);\n  --sl-color-purple-500: hsl(270.7 96.4% 62.1%);\n  --sl-color-purple-600: hsl(270.5 100% 71.9%);\n  --sl-color-purple-700: hsl(270.9 100% 81.3%);\n  --sl-color-purple-800: hsl(272.4 100% 87.7%);\n  --sl-color-purple-900: hsl(276.7 100% 91.5%);\n  --sl-color-purple-950: hsl(300 100% 96.5%);\n\n  /* Fuchsia */\n\n  --sl-color-fuchsia-50: hsl(297.1 51.2% 18.6%);\n  --sl-color-fuchsia-100: hsl(296.7 59.5% 31.5%);\n  --sl-color-fuchsia-200: hsl(295.4 65.4% 35.1%);\n  --sl-color-fuchsia-300: hsl(294.6 67.4% 42.2%);\n  --sl-color-fuchsia-400: hsl(293.3 68.7% 51.2%);\n  --sl-color-fuchsia-500: hsl(292.1 88.4% 57.7%);\n  --sl-color-fuchsia-600: hsl(292 98.5% 59.5%);\n  --sl-color-fuchsia-700: hsl(292.4 100% 79.5%);\n  --sl-color-fuchsia-800: hsl(292.9 100% 86.8%);\n  --sl-color-fuchsia-900: hsl(300 100% 91.5%);\n  --sl-color-fuchsia-950: hsl(300 100% 96.3%);\n\n  /* Pink */\n  --sl-color-pink-50: hsl(336.2 59.6% 20%);\n  --sl-color-pink-100: hsl(336.8 63.9% 34%);\n  --sl-color-pink-200: hsl(336.8 68.7% 37.6%);\n  --sl-color-pink-300: hsl(336.1 71.8% 44.5%);\n  --sl-color-pink-400: hsl(333.9 74.9% 53.1%);\n  --sl-color-pink-500: hsl(330.7 86.3% 57.7%);\n  --sl-color-pink-600: hsl(328.6 91.5% 67.2%);\n  --sl-color-pink-700: hsl(327.4 97.6% 78.7%);\n  --sl-color-pink-800: hsl(325.1 100% 86.6%);\n  --sl-color-pink-900: hsl(322.1 100% 91.3%);\n  --sl-color-pink-950: hsl(315 100% 95.9%);\n\n  /* Rose */\n  --sl-color-rose-50: hsl(342.3 62.9% 21.5%);\n  --sl-color-rose-100: hsl(342.8 68.9% 34.2%);\n  --sl-color-rose-200: hsl(344.8 72.6% 37.3%);\n  --sl-color-rose-300: hsl(346.9 75.8% 43.7%);\n  --sl-color-rose-400: hsl(348.2 80.1% 52.7%);\n  --sl-color-rose-500: hsl(350.4 94.8% 57.5%);\n  --sl-color-rose-600: hsl(351.2 100% 58.1%);\n  --sl-color-rose-700: hsl(352.3 100% 78.1%);\n  --sl-color-rose-800: hsl(352 100% 86.2%);\n  --sl-color-rose-900: hsl(354.5 100% 90.7%);\n  --sl-color-rose-950: hsl(353.3 100% 95.7%);\n\n  /*\n   * Theme Tokens\n   */\n\n  /* Primary */\n  --sl-color-primary-50: var(--sl-color-sky-50);\n  --sl-color-primary-100: var(--sl-color-sky-100);\n  --sl-color-primary-200: var(--sl-color-sky-200);\n  --sl-color-primary-300: var(--sl-color-sky-300);\n  --sl-color-primary-400: var(--sl-color-sky-400);\n  --sl-color-primary-500: var(--sl-color-sky-500);\n  --sl-color-primary-600: var(--sl-color-sky-600);\n  --sl-color-primary-700: var(--sl-color-sky-700);\n  --sl-color-primary-800: var(--sl-color-sky-800);\n  --sl-color-primary-900: var(--sl-color-sky-900);\n  --sl-color-primary-950: var(--sl-color-sky-950);\n\n  /* Success */\n  --sl-color-success-50: var(--sl-color-green-50);\n  --sl-color-success-100: var(--sl-color-green-100);\n  --sl-color-success-200: var(--sl-color-green-200);\n  --sl-color-success-300: var(--sl-color-green-300);\n  --sl-color-success-400: var(--sl-color-green-400);\n  --sl-color-success-500: var(--sl-color-green-500);\n  --sl-color-success-600: var(--sl-color-green-600);\n  --sl-color-success-700: var(--sl-color-green-700);\n  --sl-color-success-800: var(--sl-color-green-800);\n  --sl-color-success-900: var(--sl-color-green-900);\n  --sl-color-success-950: var(--sl-color-green-950);\n\n  /* Warning */\n  --sl-color-warning-50: var(--sl-color-amber-50);\n  --sl-color-warning-100: var(--sl-color-amber-100);\n  --sl-color-warning-200: var(--sl-color-amber-200);\n  --sl-color-warning-300: var(--sl-color-amber-300);\n  --sl-color-warning-400: var(--sl-color-amber-400);\n  --sl-color-warning-500: var(--sl-color-amber-500);\n  --sl-color-warning-600: var(--sl-color-amber-600);\n  --sl-color-warning-700: var(--sl-color-amber-700);\n  --sl-color-warning-800: var(--sl-color-amber-800);\n  --sl-color-warning-900: var(--sl-color-amber-900);\n  --sl-color-warning-950: var(--sl-color-amber-950);\n\n  /* Danger */\n  --sl-color-danger-50: var(--sl-color-red-50);\n  --sl-color-danger-100: var(--sl-color-red-100);\n  --sl-color-danger-200: var(--sl-color-red-200);\n  --sl-color-danger-300: var(--sl-color-red-300);\n  --sl-color-danger-400: var(--sl-color-red-400);\n  --sl-color-danger-500: var(--sl-color-red-500);\n  --sl-color-danger-600: var(--sl-color-red-600);\n  --sl-color-danger-700: var(--sl-color-red-700);\n  --sl-color-danger-800: var(--sl-color-red-800);\n  --sl-color-danger-900: var(--sl-color-red-900);\n  --sl-color-danger-950: var(--sl-color-red-950);\n\n  /* Neutral */\n  --sl-color-neutral-50: var(--sl-color-gray-50);\n  --sl-color-neutral-100: var(--sl-color-gray-100);\n  --sl-color-neutral-200: var(--sl-color-gray-200);\n  --sl-color-neutral-300: var(--sl-color-gray-300);\n  --sl-color-neutral-400: var(--sl-color-gray-400);\n  --sl-color-neutral-500: var(--sl-color-gray-500);\n  --sl-color-neutral-600: var(--sl-color-gray-600);\n  --sl-color-neutral-700: var(--sl-color-gray-700);\n  --sl-color-neutral-800: var(--sl-color-gray-800);\n  --sl-color-neutral-900: var(--sl-color-gray-900);\n  --sl-color-neutral-950: var(--sl-color-gray-950);\n\n  /* Neutral one-offs */\n  --sl-color-neutral-0: hsl(240, 5.9%, 11%);\n  --sl-color-neutral-1000: hsl(0, 0%, 100%);\n\n  /*\n   * Border radii\n   */\n\n  --sl-border-radius-small: 0.1875rem; /* 3px */\n  --sl-border-radius-medium: 0.25rem; /* 4px */\n  --sl-border-radius-large: 0.5rem; /* 8px */\n  --sl-border-radius-x-large: 1rem; /* 16px */\n\n  --sl-border-radius-circle: 50%;\n  --sl-border-radius-pill: 9999px;\n\n  /*\n   * Elevations\n   */\n\n  --sl-shadow-x-small: 0 1px 2px rgb(0 0 0 / 18%);\n  --sl-shadow-small: 0 1px 2px rgb(0 0 0 / 24%);\n  --sl-shadow-medium: 0 2px 4px rgb(0 0 0 / 24%);\n  --sl-shadow-large: 0 2px 8px rgb(0 0 0 / 24%);\n  --sl-shadow-x-large: 0 4px 16px rgb(0 0 0 / 24%);\n\n  /*\n   * Spacings\n   */\n\n  --sl-spacing-3x-small: 0.125rem; /* 2px */\n  --sl-spacing-2x-small: 0.25rem; /* 4px */\n  --sl-spacing-x-small: 0.5rem; /* 8px */\n  --sl-spacing-small: 0.75rem; /* 12px */\n  --sl-spacing-medium: 1rem; /* 16px */\n  --sl-spacing-large: 1.25rem; /* 20px */\n  --sl-spacing-x-large: 1.75rem; /* 28px */\n  --sl-spacing-2x-large: 2.25rem; /* 36px */\n  --sl-spacing-3x-large: 3rem; /* 48px */\n  --sl-spacing-4x-large: 4.5rem; /* 72px */\n\n  /*\n   * Transitions\n   */\n\n  --sl-transition-x-slow: 1000ms;\n  --sl-transition-slow: 500ms;\n  --sl-transition-medium: 250ms;\n  --sl-transition-fast: 150ms;\n  --sl-transition-x-fast: 50ms;\n\n  /*\n   * Typography\n   */\n\n  /* Fonts */\n  --sl-font-mono: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;\n  --sl-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,\n    'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n  --sl-font-serif: Georgia, 'Times New Roman', serif;\n\n  /* Font sizes */\n  --sl-font-size-2x-small: 0.625rem; /* 10px */\n  --sl-font-size-x-small: 0.75rem; /* 12px */\n  --sl-font-size-small: 0.875rem; /* 14px */\n  --sl-font-size-medium: 1rem; /* 16px */\n  --sl-font-size-large: 1.25rem; /* 20px */\n  --sl-font-size-x-large: 1.5rem; /* 24px */\n  --sl-font-size-2x-large: 2.25rem; /* 36px */\n  --sl-font-size-3x-large: 3rem; /* 48px */\n  --sl-font-size-4x-large: 4.5rem; /* 72px */\n\n  /* Font weights */\n  --sl-font-weight-light: 300;\n  --sl-font-weight-normal: 400;\n  --sl-font-weight-semibold: 500;\n  --sl-font-weight-bold: 700;\n\n  /* Letter spacings */\n  --sl-letter-spacing-denser: -0.03em;\n  --sl-letter-spacing-dense: -0.015em;\n  --sl-letter-spacing-normal: normal;\n  --sl-letter-spacing-loose: 0.075em;\n  --sl-letter-spacing-looser: 0.15em;\n\n  /* Line heights */\n  --sl-line-height-denser: 1;\n  --sl-line-height-dense: 1.4;\n  --sl-line-height-normal: 1.8;\n  --sl-line-height-loose: 2.2;\n  --sl-line-height-looser: 2.6;\n\n  /* Focus rings */\n  --sl-focus-ring-color: var(--sl-color-primary-700);\n  --sl-focus-ring-style: solid;\n  --sl-focus-ring-width: 3px;\n  --sl-focus-ring: var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color);\n  --sl-focus-ring-offset: 1px;\n\n  /*\n   * Forms\n   */\n\n  /* Buttons */\n  --sl-button-font-size-small: var(--sl-font-size-x-small);\n  --sl-button-font-size-medium: var(--sl-font-size-small);\n  --sl-button-font-size-large: var(--sl-font-size-medium);\n\n  /* Inputs */\n  --sl-input-height-small: 1.875rem; /* 30px */\n  --sl-input-height-medium: 2.5rem; /* 40px */\n  --sl-input-height-large: 3.125rem; /* 50px */\n\n  --sl-input-background-color: var(--sl-color-neutral-0);\n  --sl-input-background-color-hover: var(--sl-input-background-color);\n  --sl-input-background-color-focus: var(--sl-input-background-color);\n  --sl-input-background-color-disabled: var(--sl-color-neutral-100);\n  --sl-input-border-color: var(--sl-color-neutral-400);\n  --sl-input-border-color-hover: var(--sl-color-neutral-500);\n  --sl-input-border-color-focus: var(--sl-color-primary-600);\n  --sl-input-border-color-disabled: var(--sl-color-neutral-400);\n  --sl-input-border-width: 1px;\n  --sl-input-required-content: '*';\n  --sl-input-required-content-offset: -2px;\n  --sl-input-required-content-color: var(--sl-input-label-color);\n\n  --sl-input-border-radius-small: var(--sl-border-radius-medium);\n  --sl-input-border-radius-medium: var(--sl-border-radius-medium);\n  --sl-input-border-radius-large: var(--sl-border-radius-medium);\n\n  --sl-input-font-family: var(--sl-font-sans);\n  --sl-input-font-weight: var(--sl-font-weight-normal);\n  --sl-input-font-size-small: var(--sl-font-size-small);\n  --sl-input-font-size-medium: var(--sl-font-size-medium);\n  --sl-input-font-size-large: var(--sl-font-size-large);\n  --sl-input-letter-spacing: var(--sl-letter-spacing-normal);\n\n  --sl-input-color: var(--sl-color-neutral-700);\n  --sl-input-color-hover: var(--sl-color-neutral-700);\n  --sl-input-color-focus: var(--sl-color-neutral-700);\n  --sl-input-color-disabled: var(--sl-color-neutral-900);\n  --sl-input-icon-color: var(--sl-color-neutral-500);\n  --sl-input-icon-color-hover: var(--sl-color-neutral-600);\n  --sl-input-icon-color-focus: var(--sl-color-neutral-600);\n  --sl-input-placeholder-color: var(--sl-color-neutral-500);\n  --sl-input-placeholder-color-disabled: var(--sl-color-neutral-600);\n  --sl-input-spacing-small: var(--sl-spacing-small);\n  --sl-input-spacing-medium: var(--sl-spacing-medium);\n  --sl-input-spacing-large: var(--sl-spacing-large);\n\n  --sl-input-focus-ring-color: hsl(198.6 88.7% 48.4% / 40%);\n  --sl-input-focus-ring-offset: 0;\n\n  --sl-input-filled-background-color: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-hover: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-focus: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-disabled: var(--sl-color-neutral-100);\n  --sl-input-filled-color: var(--sl-color-neutral-800);\n  --sl-input-filled-color-hover: var(--sl-color-neutral-800);\n  --sl-input-filled-color-focus: var(--sl-color-neutral-700);\n  --sl-input-filled-color-disabled: var(--sl-color-neutral-800);\n\n  /* Labels */\n  --sl-input-label-font-size-small: var(--sl-font-size-small);\n  --sl-input-label-font-size-medium: var(--sl-font-size-medium);\n  --sl-input-label-font-size-large: var(--sl-font-size-large);\n  --sl-input-label-color: inherit;\n\n  /* Help text */\n  --sl-input-help-text-font-size-small: var(--sl-font-size-x-small);\n  --sl-input-help-text-font-size-medium: var(--sl-font-size-small);\n  --sl-input-help-text-font-size-large: var(--sl-font-size-medium);\n  --sl-input-help-text-color: var(--sl-color-neutral-600);\n\n  /* Toggles (checkboxes, radios, switches) */\n  --sl-toggle-size-small: 0.875rem; /* 14px */\n  --sl-toggle-size-medium: 1.125rem; /* 18px */\n  --sl-toggle-size-large: 1.375rem; /* 22px */\n\n  /*\n   * Overlays\n   */\n\n  --sl-overlay-background-color: hsl(0 0% 0% / 43%);\n\n  /*\n   * Panels\n   */\n\n  --sl-panel-background-color: var(--sl-color-neutral-50);\n  --sl-panel-border-color: var(--sl-color-neutral-200);\n  --sl-panel-border-width: 1px;\n\n  /*\n   * Tooltips\n   */\n\n  --sl-tooltip-border-radius: var(--sl-border-radius-medium);\n  --sl-tooltip-background-color: var(--sl-color-neutral-800);\n  --sl-tooltip-color: var(--sl-color-neutral-0);\n  --sl-tooltip-font-family: var(--sl-font-sans);\n  --sl-tooltip-font-weight: var(--sl-font-weight-normal);\n  --sl-tooltip-font-size: var(--sl-font-size-small);\n  --sl-tooltip-line-height: var(--sl-line-height-dense);\n  --sl-tooltip-padding: var(--sl-spacing-2x-small) var(--sl-spacing-x-small);\n  --sl-tooltip-arrow-size: 6px;\n\n  /*\n   * Z-indexes\n   */\n\n  --sl-z-index-drawer: 700;\n  --sl-z-index-dialog: 800;\n  --sl-z-index-dropdown: 900;\n  --sl-z-index-toast: 950;\n  --sl-z-index-tooltip: 1000;\n}\n\n/* _utility.css */\n"
  },
  {
    "path": "src/themes/light.css",
    "content": ":root,\n:host,\n.sl-theme-light {\n  color-scheme: light;\n\n  /*\n   * Color Primitives\n   */\n\n  /* Gray */\n  --sl-color-gray-50: hsl(0 0% 97.5%);\n  --sl-color-gray-100: hsl(240 4.8% 95.9%);\n  --sl-color-gray-200: hsl(240 5.9% 90%);\n  --sl-color-gray-300: hsl(240 4.9% 83.9%);\n  --sl-color-gray-400: hsl(240 5% 64.9%);\n  --sl-color-gray-500: hsl(240 3.8% 46.1%);\n  --sl-color-gray-600: hsl(240 5.2% 33.9%);\n  --sl-color-gray-700: hsl(240 5.3% 26.1%);\n  --sl-color-gray-800: hsl(240 3.7% 15.9%);\n  --sl-color-gray-900: hsl(240 5.9% 10%);\n  --sl-color-gray-950: hsl(240 7.3% 8%);\n\n  /* Red */\n  --sl-color-red-50: hsl(0 85.7% 97.3%);\n  --sl-color-red-100: hsl(0 93.3% 94.1%);\n  --sl-color-red-200: hsl(0 96.3% 89.4%);\n  --sl-color-red-300: hsl(0 93.5% 81.8%);\n  --sl-color-red-400: hsl(0 90.6% 70.8%);\n  --sl-color-red-500: hsl(0 84.2% 60.2%);\n  --sl-color-red-600: hsl(0 72.2% 50.6%);\n  --sl-color-red-700: hsl(0 73.7% 41.8%);\n  --sl-color-red-800: hsl(0 70% 35.3%);\n  --sl-color-red-900: hsl(0 62.8% 30.6%);\n  --sl-color-red-950: hsl(0 60% 19.6%);\n\n  /* Orange */\n  --sl-color-orange-50: hsl(33.3 100% 96.5%);\n  --sl-color-orange-100: hsl(34.3 100% 91.8%);\n  --sl-color-orange-200: hsl(32.1 97.7% 83.1%);\n  --sl-color-orange-300: hsl(30.7 97.2% 72.4%);\n  --sl-color-orange-400: hsl(27 96% 61%);\n  --sl-color-orange-500: hsl(24.6 95% 53.1%);\n  --sl-color-orange-600: hsl(20.5 90.2% 48.2%);\n  --sl-color-orange-700: hsl(17.5 88.3% 40.4%);\n  --sl-color-orange-800: hsl(15 79.1% 33.7%);\n  --sl-color-orange-900: hsl(15.3 74.6% 27.8%);\n  --sl-color-orange-950: hsl(15.2 69.1% 19%);\n\n  /* Amber */\n  --sl-color-amber-50: hsl(48 100% 96.1%);\n  --sl-color-amber-100: hsl(48 96.5% 88.8%);\n  --sl-color-amber-200: hsl(48 96.6% 76.7%);\n  --sl-color-amber-300: hsl(45.9 96.7% 64.5%);\n  --sl-color-amber-400: hsl(43.3 96.4% 56.3%);\n  --sl-color-amber-500: hsl(37.7 92.1% 50.2%);\n  --sl-color-amber-600: hsl(32.1 94.6% 43.7%);\n  --sl-color-amber-700: hsl(26 90.5% 37.1%);\n  --sl-color-amber-800: hsl(22.7 82.5% 31.4%);\n  --sl-color-amber-900: hsl(21.7 77.8% 26.5%);\n  --sl-color-amber-950: hsl(22.9 74.1% 16.7%);\n\n  /* Yellow */\n  --sl-color-yellow-50: hsl(54.5 91.7% 95.3%);\n  --sl-color-yellow-100: hsl(54.9 96.7% 88%);\n  --sl-color-yellow-200: hsl(52.8 98.3% 76.9%);\n  --sl-color-yellow-300: hsl(50.4 97.8% 63.5%);\n  --sl-color-yellow-400: hsl(47.9 95.8% 53.1%);\n  --sl-color-yellow-500: hsl(45.4 93.4% 47.5%);\n  --sl-color-yellow-600: hsl(40.6 96.1% 40.4%);\n  --sl-color-yellow-700: hsl(35.5 91.7% 32.9%);\n  --sl-color-yellow-800: hsl(31.8 81% 28.8%);\n  --sl-color-yellow-900: hsl(28.4 72.5% 25.7%);\n  --sl-color-yellow-950: hsl(33.1 69% 13.9%);\n\n  /* Lime */\n  --sl-color-lime-50: hsl(78.3 92% 95.1%);\n  --sl-color-lime-100: hsl(79.6 89.1% 89.2%);\n  --sl-color-lime-200: hsl(80.9 88.5% 79.6%);\n  --sl-color-lime-300: hsl(82 84.5% 67.1%);\n  --sl-color-lime-400: hsl(82.7 78% 55.5%);\n  --sl-color-lime-500: hsl(83.7 80.5% 44.3%);\n  --sl-color-lime-600: hsl(84.8 85.2% 34.5%);\n  --sl-color-lime-700: hsl(85.9 78.4% 27.3%);\n  --sl-color-lime-800: hsl(86.3 69% 22.7%);\n  --sl-color-lime-900: hsl(87.6 61.2% 20.2%);\n  --sl-color-lime-950: hsl(86.5 60.6% 13.9%);\n\n  /* Green */\n  --sl-color-green-50: hsl(138.5 76.5% 96.7%);\n  --sl-color-green-100: hsl(140.6 84.2% 92.5%);\n  --sl-color-green-200: hsl(141 78.9% 85.1%);\n  --sl-color-green-300: hsl(141.7 76.6% 73.1%);\n  --sl-color-green-400: hsl(141.9 69.2% 58%);\n  --sl-color-green-500: hsl(142.1 70.6% 45.3%);\n  --sl-color-green-600: hsl(142.1 76.2% 36.3%);\n  --sl-color-green-700: hsl(142.4 71.8% 29.2%);\n  --sl-color-green-800: hsl(142.8 64.2% 24.1%);\n  --sl-color-green-900: hsl(143.8 61.2% 20.2%);\n  --sl-color-green-950: hsl(144.3 60.7% 12%);\n\n  /* Emerald */\n  --sl-color-emerald-50: hsl(151.8 81% 95.9%);\n  --sl-color-emerald-100: hsl(149.3 80.4% 90%);\n  --sl-color-emerald-200: hsl(152.4 76% 80.4%);\n  --sl-color-emerald-300: hsl(156.2 71.6% 66.9%);\n  --sl-color-emerald-400: hsl(158.1 64.4% 51.6%);\n  --sl-color-emerald-500: hsl(160.1 84.1% 39.4%);\n  --sl-color-emerald-600: hsl(161.4 93.5% 30.4%);\n  --sl-color-emerald-700: hsl(162.9 93.5% 24.3%);\n  --sl-color-emerald-800: hsl(163.1 88.1% 19.8%);\n  --sl-color-emerald-900: hsl(164.2 85.7% 16.5%);\n  --sl-color-emerald-950: hsl(164.3 87.5% 9.4%);\n\n  /* Teal */\n  --sl-color-teal-50: hsl(166.2 76.5% 96.7%);\n  --sl-color-teal-100: hsl(167.2 85.5% 89.2%);\n  --sl-color-teal-200: hsl(168.4 83.8% 78.2%);\n  --sl-color-teal-300: hsl(170.6 76.9% 64.3%);\n  --sl-color-teal-400: hsl(172.5 66% 50.4%);\n  --sl-color-teal-500: hsl(173.4 80.4% 40%);\n  --sl-color-teal-600: hsl(174.7 83.9% 31.6%);\n  --sl-color-teal-700: hsl(175.3 77.4% 26.1%);\n  --sl-color-teal-800: hsl(176.1 69.4% 21.8%);\n  --sl-color-teal-900: hsl(175.9 60.8% 19%);\n  --sl-color-teal-950: hsl(176.5 58.6% 11.4%);\n\n  /* Cyan */\n  --sl-color-cyan-50: hsl(183.2 100% 96.3%);\n  --sl-color-cyan-100: hsl(185.1 95.9% 90.4%);\n  --sl-color-cyan-200: hsl(186.2 93.5% 81.8%);\n  --sl-color-cyan-300: hsl(187 92.4% 69%);\n  --sl-color-cyan-400: hsl(187.9 85.7% 53.3%);\n  --sl-color-cyan-500: hsl(188.7 94.5% 42.7%);\n  --sl-color-cyan-600: hsl(191.6 91.4% 36.5%);\n  --sl-color-cyan-700: hsl(192.9 82.3% 31%);\n  --sl-color-cyan-800: hsl(194.4 69.6% 27.1%);\n  --sl-color-cyan-900: hsl(196.4 63.6% 23.7%);\n  --sl-color-cyan-950: hsl(196.8 61% 16.1%);\n\n  /* Sky */\n  --sl-color-sky-50: hsl(204 100% 97.1%);\n  --sl-color-sky-100: hsl(204 93.8% 93.7%);\n  --sl-color-sky-200: hsl(200.6 94.4% 86.1%);\n  --sl-color-sky-300: hsl(199.4 95.5% 73.9%);\n  --sl-color-sky-400: hsl(198.4 93.2% 59.6%);\n  --sl-color-sky-500: hsl(198.6 88.7% 48.4%);\n  --sl-color-sky-600: hsl(200.4 98% 39.4%);\n  --sl-color-sky-700: hsl(201.3 96.3% 32.2%);\n  --sl-color-sky-800: hsl(201 90% 27.5%);\n  --sl-color-sky-900: hsl(202 80.3% 23.9%);\n  --sl-color-sky-950: hsl(202.3 73.8% 16.5%);\n\n  /* Blue */\n  --sl-color-blue-50: hsl(213.8 100% 96.9%);\n  --sl-color-blue-100: hsl(214.3 94.6% 92.7%);\n  --sl-color-blue-200: hsl(213.3 96.9% 87.3%);\n  --sl-color-blue-300: hsl(211.7 96.4% 78.4%);\n  --sl-color-blue-400: hsl(213.1 93.9% 67.8%);\n  --sl-color-blue-500: hsl(217.2 91.2% 59.8%);\n  --sl-color-blue-600: hsl(221.2 83.2% 53.3%);\n  --sl-color-blue-700: hsl(224.3 76.3% 48%);\n  --sl-color-blue-800: hsl(225.9 70.7% 40.2%);\n  --sl-color-blue-900: hsl(224.4 64.3% 32.9%);\n  --sl-color-blue-950: hsl(226.2 55.3% 18.4%);\n\n  /* Indigo */\n  --sl-color-indigo-50: hsl(225.9 100% 96.7%);\n  --sl-color-indigo-100: hsl(226.5 100% 93.9%);\n  --sl-color-indigo-200: hsl(228 96.5% 88.8%);\n  --sl-color-indigo-300: hsl(229.7 93.5% 81.8%);\n  --sl-color-indigo-400: hsl(234.5 89.5% 73.9%);\n  --sl-color-indigo-500: hsl(238.7 83.5% 66.7%);\n  --sl-color-indigo-600: hsl(243.4 75.4% 58.6%);\n  --sl-color-indigo-700: hsl(244.5 57.9% 50.6%);\n  --sl-color-indigo-800: hsl(243.7 54.5% 41.4%);\n  --sl-color-indigo-900: hsl(242.2 47.4% 34.3%);\n  --sl-color-indigo-950: hsl(243.5 43.6% 22.9%);\n\n  /* Violet */\n  --sl-color-violet-50: hsl(250 100% 97.6%);\n  --sl-color-violet-100: hsl(251.4 91.3% 95.5%);\n  --sl-color-violet-200: hsl(250.5 95.2% 91.8%);\n  --sl-color-violet-300: hsl(252.5 94.7% 85.1%);\n  --sl-color-violet-400: hsl(255.1 91.7% 76.3%);\n  --sl-color-violet-500: hsl(258.3 89.5% 66.3%);\n  --sl-color-violet-600: hsl(262.1 83.3% 57.8%);\n  --sl-color-violet-700: hsl(263.4 70% 50.4%);\n  --sl-color-violet-800: hsl(263.4 69.3% 42.2%);\n  --sl-color-violet-900: hsl(263.5 67.4% 34.9%);\n  --sl-color-violet-950: hsl(265.1 61.5% 21.4%);\n\n  /* Purple */\n  --sl-color-purple-50: hsl(270 100% 98%);\n  --sl-color-purple-100: hsl(268.7 100% 95.5%);\n  --sl-color-purple-200: hsl(268.6 100% 91.8%);\n  --sl-color-purple-300: hsl(269.2 97.4% 85.1%);\n  --sl-color-purple-400: hsl(270 95.2% 75.3%);\n  --sl-color-purple-500: hsl(270.7 91% 65.1%);\n  --sl-color-purple-600: hsl(271.5 81.3% 55.9%);\n  --sl-color-purple-700: hsl(272.1 71.7% 47.1%);\n  --sl-color-purple-800: hsl(272.9 67.2% 39.4%);\n  --sl-color-purple-900: hsl(273.6 65.6% 32%);\n  --sl-color-purple-950: hsl(276 59.5% 16.5%);\n\n  /* Fuchsia */\n  --sl-color-fuchsia-50: hsl(289.1 100% 97.8%);\n  --sl-color-fuchsia-100: hsl(287 100% 95.5%);\n  --sl-color-fuchsia-200: hsl(288.3 95.8% 90.6%);\n  --sl-color-fuchsia-300: hsl(291.1 93.1% 82.9%);\n  --sl-color-fuchsia-400: hsl(292 91.4% 72.5%);\n  --sl-color-fuchsia-500: hsl(292.2 84.1% 60.6%);\n  --sl-color-fuchsia-600: hsl(293.4 69.5% 48.8%);\n  --sl-color-fuchsia-700: hsl(294.7 72.4% 39.8%);\n  --sl-color-fuchsia-800: hsl(295.4 70.2% 32.9%);\n  --sl-color-fuchsia-900: hsl(296.7 63.6% 28%);\n  --sl-color-fuchsia-950: hsl(297.1 56.8% 14.5%);\n\n  /* Pink */\n  --sl-color-pink-50: hsl(327.3 73.3% 97.1%);\n  --sl-color-pink-100: hsl(325.7 77.8% 94.7%);\n  --sl-color-pink-200: hsl(325.9 84.6% 89.8%);\n  --sl-color-pink-300: hsl(327.4 87.1% 81.8%);\n  --sl-color-pink-400: hsl(328.6 85.5% 70.2%);\n  --sl-color-pink-500: hsl(330.4 81.2% 60.4%);\n  --sl-color-pink-600: hsl(333.3 71.4% 50.6%);\n  --sl-color-pink-700: hsl(335.1 77.6% 42%);\n  --sl-color-pink-800: hsl(335.8 74.4% 35.3%);\n  --sl-color-pink-900: hsl(335.9 69% 30.4%);\n  --sl-color-pink-950: hsl(336.2 65.4% 15.9%);\n\n  /* Rose */\n  --sl-color-rose-50: hsl(355.7 100% 97.3%);\n  --sl-color-rose-100: hsl(355.6 100% 94.7%);\n  --sl-color-rose-200: hsl(352.7 96.1% 90%);\n  --sl-color-rose-300: hsl(352.6 95.7% 81.8%);\n  --sl-color-rose-400: hsl(351.3 94.5% 71.4%);\n  --sl-color-rose-500: hsl(349.7 89.2% 60.2%);\n  --sl-color-rose-600: hsl(346.8 77.2% 49.8%);\n  --sl-color-rose-700: hsl(345.3 82.7% 40.8%);\n  --sl-color-rose-800: hsl(343.4 79.7% 34.7%);\n  --sl-color-rose-900: hsl(341.5 75.5% 30.4%);\n  --sl-color-rose-950: hsl(341.3 70.1% 17.1%);\n\n  /*\n   * Theme Tokens\n   */\n\n  /* Primary */\n  --sl-color-primary-50: var(--sl-color-sky-50);\n  --sl-color-primary-100: var(--sl-color-sky-100);\n  --sl-color-primary-200: var(--sl-color-sky-200);\n  --sl-color-primary-300: var(--sl-color-sky-300);\n  --sl-color-primary-400: var(--sl-color-sky-400);\n  --sl-color-primary-500: var(--sl-color-sky-500);\n  --sl-color-primary-600: var(--sl-color-sky-600);\n  --sl-color-primary-700: var(--sl-color-sky-700);\n  --sl-color-primary-800: var(--sl-color-sky-800);\n  --sl-color-primary-900: var(--sl-color-sky-900);\n  --sl-color-primary-950: var(--sl-color-sky-950);\n\n  /* Success */\n  --sl-color-success-50: var(--sl-color-green-50);\n  --sl-color-success-100: var(--sl-color-green-100);\n  --sl-color-success-200: var(--sl-color-green-200);\n  --sl-color-success-300: var(--sl-color-green-300);\n  --sl-color-success-400: var(--sl-color-green-400);\n  --sl-color-success-500: var(--sl-color-green-500);\n  --sl-color-success-600: var(--sl-color-green-600);\n  --sl-color-success-700: var(--sl-color-green-700);\n  --sl-color-success-800: var(--sl-color-green-800);\n  --sl-color-success-900: var(--sl-color-green-900);\n  --sl-color-success-950: var(--sl-color-green-950);\n\n  /* Warning */\n  --sl-color-warning-50: var(--sl-color-amber-50);\n  --sl-color-warning-100: var(--sl-color-amber-100);\n  --sl-color-warning-200: var(--sl-color-amber-200);\n  --sl-color-warning-300: var(--sl-color-amber-300);\n  --sl-color-warning-400: var(--sl-color-amber-400);\n  --sl-color-warning-500: var(--sl-color-amber-500);\n  --sl-color-warning-600: var(--sl-color-amber-600);\n  --sl-color-warning-700: var(--sl-color-amber-700);\n  --sl-color-warning-800: var(--sl-color-amber-800);\n  --sl-color-warning-900: var(--sl-color-amber-900);\n  --sl-color-warning-950: var(--sl-color-amber-950);\n\n  /* Danger */\n  --sl-color-danger-50: var(--sl-color-red-50);\n  --sl-color-danger-100: var(--sl-color-red-100);\n  --sl-color-danger-200: var(--sl-color-red-200);\n  --sl-color-danger-300: var(--sl-color-red-300);\n  --sl-color-danger-400: var(--sl-color-red-400);\n  --sl-color-danger-500: var(--sl-color-red-500);\n  --sl-color-danger-600: var(--sl-color-red-600);\n  --sl-color-danger-700: var(--sl-color-red-700);\n  --sl-color-danger-800: var(--sl-color-red-800);\n  --sl-color-danger-900: var(--sl-color-red-900);\n  --sl-color-danger-950: var(--sl-color-red-950);\n\n  /* Neutral */\n  --sl-color-neutral-50: var(--sl-color-gray-50);\n  --sl-color-neutral-100: var(--sl-color-gray-100);\n  --sl-color-neutral-200: var(--sl-color-gray-200);\n  --sl-color-neutral-300: var(--sl-color-gray-300);\n  --sl-color-neutral-400: var(--sl-color-gray-400);\n  --sl-color-neutral-500: var(--sl-color-gray-500);\n  --sl-color-neutral-600: var(--sl-color-gray-600);\n  --sl-color-neutral-700: var(--sl-color-gray-700);\n  --sl-color-neutral-800: var(--sl-color-gray-800);\n  --sl-color-neutral-900: var(--sl-color-gray-900);\n  --sl-color-neutral-950: var(--sl-color-gray-950);\n\n  /* Neutral one-offs */\n  --sl-color-neutral-0: hsl(0, 0%, 100%);\n  --sl-color-neutral-1000: hsl(0, 0%, 0%);\n\n  /*\n   * Border radii\n   */\n\n  --sl-border-radius-small: 0.1875rem; /* 3px */\n  --sl-border-radius-medium: 0.25rem; /* 4px */\n  --sl-border-radius-large: 0.5rem; /* 8px */\n  --sl-border-radius-x-large: 1rem; /* 16px */\n\n  --sl-border-radius-circle: 50%;\n  --sl-border-radius-pill: 9999px;\n\n  /*\n   * Elevations\n   */\n\n  --sl-shadow-x-small: 0 1px 2px hsl(240 3.8% 46.1% / 6%);\n  --sl-shadow-small: 0 1px 2px hsl(240 3.8% 46.1% / 12%);\n  --sl-shadow-medium: 0 2px 4px hsl(240 3.8% 46.1% / 12%);\n  --sl-shadow-large: 0 2px 8px hsl(240 3.8% 46.1% / 12%);\n  --sl-shadow-x-large: 0 4px 16px hsl(240 3.8% 46.1% / 12%);\n\n  /*\n   * Spacings\n   */\n\n  --sl-spacing-3x-small: 0.125rem; /* 2px */\n  --sl-spacing-2x-small: 0.25rem; /* 4px */\n  --sl-spacing-x-small: 0.5rem; /* 8px */\n  --sl-spacing-small: 0.75rem; /* 12px */\n  --sl-spacing-medium: 1rem; /* 16px */\n  --sl-spacing-large: 1.25rem; /* 20px */\n  --sl-spacing-x-large: 1.75rem; /* 28px */\n  --sl-spacing-2x-large: 2.25rem; /* 36px */\n  --sl-spacing-3x-large: 3rem; /* 48px */\n  --sl-spacing-4x-large: 4.5rem; /* 72px */\n\n  /*\n   * Transitions\n   */\n\n  --sl-transition-x-slow: 1000ms;\n  --sl-transition-slow: 500ms;\n  --sl-transition-medium: 250ms;\n  --sl-transition-fast: 150ms;\n  --sl-transition-x-fast: 50ms;\n\n  /*\n   * Typography\n   */\n\n  /* Fonts */\n  --sl-font-mono: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;\n  --sl-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,\n    'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n  --sl-font-serif: Georgia, 'Times New Roman', serif;\n\n  /* Font sizes */\n  --sl-font-size-2x-small: 0.625rem; /* 10px */\n  --sl-font-size-x-small: 0.75rem; /* 12px */\n  --sl-font-size-small: 0.875rem; /* 14px */\n  --sl-font-size-medium: 1rem; /* 16px */\n  --sl-font-size-large: 1.25rem; /* 20px */\n  --sl-font-size-x-large: 1.5rem; /* 24px */\n  --sl-font-size-2x-large: 2.25rem; /* 36px */\n  --sl-font-size-3x-large: 3rem; /* 48px */\n  --sl-font-size-4x-large: 4.5rem; /* 72px */\n\n  /* Font weights */\n  --sl-font-weight-light: 300;\n  --sl-font-weight-normal: 400;\n  --sl-font-weight-semibold: 500;\n  --sl-font-weight-bold: 700;\n\n  /* Letter spacings */\n  --sl-letter-spacing-denser: -0.03em;\n  --sl-letter-spacing-dense: -0.015em;\n  --sl-letter-spacing-normal: normal;\n  --sl-letter-spacing-loose: 0.075em;\n  --sl-letter-spacing-looser: 0.15em;\n\n  /* Line heights */\n  --sl-line-height-denser: 1;\n  --sl-line-height-dense: 1.4;\n  --sl-line-height-normal: 1.8;\n  --sl-line-height-loose: 2.2;\n  --sl-line-height-looser: 2.6;\n\n  /* Focus rings */\n  --sl-focus-ring-color: var(--sl-color-primary-600);\n  --sl-focus-ring-style: solid;\n  --sl-focus-ring-width: 3px;\n  --sl-focus-ring: var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color);\n  --sl-focus-ring-offset: 1px;\n\n  /*\n   * Forms\n   */\n\n  /* Buttons */\n  --sl-button-font-size-small: var(--sl-font-size-x-small);\n  --sl-button-font-size-medium: var(--sl-font-size-small);\n  --sl-button-font-size-large: var(--sl-font-size-medium);\n\n  /* Inputs */\n  --sl-input-height-small: 1.875rem; /* 30px */\n  --sl-input-height-medium: 2.5rem; /* 40px */\n  --sl-input-height-large: 3.125rem; /* 50px */\n\n  --sl-input-background-color: var(--sl-color-neutral-0);\n  --sl-input-background-color-hover: var(--sl-input-background-color);\n  --sl-input-background-color-focus: var(--sl-input-background-color);\n  --sl-input-background-color-disabled: var(--sl-color-neutral-100);\n  --sl-input-border-color: var(--sl-color-neutral-300);\n  --sl-input-border-color-hover: var(--sl-color-neutral-400);\n  --sl-input-border-color-focus: var(--sl-color-primary-500);\n  --sl-input-border-color-disabled: var(--sl-color-neutral-300);\n  --sl-input-border-width: 1px;\n  --sl-input-required-content: '*';\n  --sl-input-required-content-offset: -2px;\n  --sl-input-required-content-color: var(--sl-input-label-color);\n\n  --sl-input-border-radius-small: var(--sl-border-radius-medium);\n  --sl-input-border-radius-medium: var(--sl-border-radius-medium);\n  --sl-input-border-radius-large: var(--sl-border-radius-medium);\n\n  --sl-input-font-family: var(--sl-font-sans);\n  --sl-input-font-weight: var(--sl-font-weight-normal);\n  --sl-input-font-size-small: var(--sl-font-size-small);\n  --sl-input-font-size-medium: var(--sl-font-size-medium);\n  --sl-input-font-size-large: var(--sl-font-size-large);\n  --sl-input-letter-spacing: var(--sl-letter-spacing-normal);\n\n  --sl-input-color: var(--sl-color-neutral-700);\n  --sl-input-color-hover: var(--sl-color-neutral-700);\n  --sl-input-color-focus: var(--sl-color-neutral-700);\n  --sl-input-color-disabled: var(--sl-color-neutral-900);\n  --sl-input-icon-color: var(--sl-color-neutral-500);\n  --sl-input-icon-color-hover: var(--sl-color-neutral-600);\n  --sl-input-icon-color-focus: var(--sl-color-neutral-600);\n  --sl-input-placeholder-color: var(--sl-color-neutral-500);\n  --sl-input-placeholder-color-disabled: var(--sl-color-neutral-600);\n  --sl-input-spacing-small: var(--sl-spacing-small);\n  --sl-input-spacing-medium: var(--sl-spacing-medium);\n  --sl-input-spacing-large: var(--sl-spacing-large);\n\n  --sl-input-focus-ring-color: hsl(198.6 88.7% 48.4% / 40%);\n  --sl-input-focus-ring-offset: 0;\n\n  --sl-input-filled-background-color: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-hover: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-focus: var(--sl-color-neutral-100);\n  --sl-input-filled-background-color-disabled: var(--sl-color-neutral-100);\n  --sl-input-filled-color: var(--sl-color-neutral-800);\n  --sl-input-filled-color-hover: var(--sl-color-neutral-800);\n  --sl-input-filled-color-focus: var(--sl-color-neutral-700);\n  --sl-input-filled-color-disabled: var(--sl-color-neutral-800);\n\n  /* Labels */\n  --sl-input-label-font-size-small: var(--sl-font-size-small);\n  --sl-input-label-font-size-medium: var(--sl-font-size-medium);\n  --sl-input-label-font-size-large: var(--sl-font-size-large);\n  --sl-input-label-color: inherit;\n\n  /* Help text */\n  --sl-input-help-text-font-size-small: var(--sl-font-size-x-small);\n  --sl-input-help-text-font-size-medium: var(--sl-font-size-small);\n  --sl-input-help-text-font-size-large: var(--sl-font-size-medium);\n  --sl-input-help-text-color: var(--sl-color-neutral-500);\n\n  /* Toggles (checkboxes, radios, switches) */\n  --sl-toggle-size-small: 0.875rem; /* 14px */\n  --sl-toggle-size-medium: 1.125rem; /* 18px */\n  --sl-toggle-size-large: 1.375rem; /* 22px */\n\n  /*\n   * Overlays\n   */\n\n  --sl-overlay-background-color: hsl(240 3.8% 46.1% / 33%);\n\n  /*\n   * Panels\n   */\n\n  --sl-panel-background-color: var(--sl-color-neutral-0);\n  --sl-panel-border-color: var(--sl-color-neutral-200);\n  --sl-panel-border-width: 1px;\n\n  /*\n   * Tooltips\n   */\n\n  --sl-tooltip-border-radius: var(--sl-border-radius-medium);\n  --sl-tooltip-background-color: var(--sl-color-neutral-800);\n  --sl-tooltip-color: var(--sl-color-neutral-0);\n  --sl-tooltip-font-family: var(--sl-font-sans);\n  --sl-tooltip-font-weight: var(--sl-font-weight-normal);\n  --sl-tooltip-font-size: var(--sl-font-size-small);\n  --sl-tooltip-line-height: var(--sl-line-height-dense);\n  --sl-tooltip-padding: var(--sl-spacing-2x-small) var(--sl-spacing-x-small);\n  --sl-tooltip-arrow-size: 6px;\n\n  /*\n   * Z-indexes\n   */\n\n  --sl-z-index-drawer: 700;\n  --sl-z-index-dialog: 800;\n  --sl-z-index-dropdown: 900;\n  --sl-z-index-toast: 950;\n  --sl-z-index-tooltip: 1000;\n}\n\n/* _utility.css */\n"
  },
  {
    "path": "src/translations/ar.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'ar',\n  $name: 'العربية',\n  $dir: 'rtl',\n\n  carousel: 'كاروسيل',\n  clearEntry: 'حذف الخيارات',\n  close: 'اغلاق',\n  copied: 'تم النسخ',\n  copy: 'نسخ',\n  currentValue: 'القيمة الحالية',\n  error: 'خطأ',\n  goToSlide: (slide, count) => `عرض شريحة رقم ${slide} من ${count}`,\n  hidePassword: 'اخفاء كلمة المرور',\n  loading: 'جاري التحميل',\n  nextSlide: 'الشريحة التالية',\n  numOptionsSelected: num => {\n    if (num === 0) return 'لم يتم تحديد أي خيارات';\n    if (num === 1) return 'تم تحديد خيار واحد';\n    if (num === 2) return 'تم تحديد خياران';\n    if (num > 2 && num < 11) return `تم تحديد ${num} خيارات`;\n    return `تم تحديد ${num} خيار`;\n  },\n  previousSlide: 'الشريحة السابقة',\n  progress: 'مقدار التقدم',\n  remove: 'حذف',\n  resize: 'تغيير الحجم',\n  scrollToEnd: 'الانتقال الى النهاية',\n  scrollToStart: 'الانتقال الى البداية',\n  selectAColorFromTheScreen: 'اختر لون من الشاشة',\n  showPassword: 'عرض كلمة المرور',\n  slideNum: slide => `شريحة ${slide}`,\n  toggleColorFormat: 'تغيير صيغة عرض  اللون'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/cs.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'cs',\n  $name: 'Čeština',\n  $dir: 'ltr',\n\n  carousel: 'Karusel',\n  clearEntry: 'Smazat položku',\n  close: 'Zavřít',\n  copied: 'Zkopírováno',\n  copy: 'Kopírovat',\n  currentValue: 'Současná hodnota',\n  error: 'Chyba',\n  goToSlide: (slide, count) => `Přejít na slide ${slide} z ${count}`,\n  hidePassword: 'Skrýt heslo',\n  loading: 'Nahrává se',\n  nextSlide: 'Další slide',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nejsou vybrány žádné možnosti';\n    if (num === 1) return 'Je vybrána jedna možnost';\n    return `Počet vybraných možností: ${num}`;\n  },\n  previousSlide: 'Předchozí slide',\n  progress: 'Průběh',\n  remove: 'Odstranit',\n  resize: 'Změnit velikost',\n  scrollToEnd: 'Scrollovat na konec',\n  scrollToStart: 'Scrollovat na začátek',\n  selectAColorFromTheScreen: 'Vybrat barvu z obrazovky',\n  showPassword: 'Zobrazit heslo',\n  slideNum: slide => `Slide ${slide}`,\n  toggleColorFormat: 'Přepnout formát barvy'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/da.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'da',\n  $name: 'Dansk',\n  $dir: 'ltr',\n\n  carousel: 'Karrusel',\n  clearEntry: 'Ryd indtastning',\n  close: 'Luk',\n  copied: 'Kopieret',\n  copy: 'Kopier',\n  currentValue: 'Nuværende værdi',\n  error: 'Fejl',\n  goToSlide: (slide, count) => `Gå til dias ${slide} af ${count}`,\n  hidePassword: 'Skjul adgangskode',\n  loading: 'Indlæser',\n  nextSlide: 'Næste slide',\n  numOptionsSelected: (num: number) => {\n    if (num === 0) return 'Ingen valgt';\n    if (num === 1) return '1 valgt';\n    return `${num} valgt`;\n  },\n  previousSlide: 'Forrige dias',\n  progress: 'Status',\n  remove: 'Fjern',\n  resize: 'Tilpas størrelse',\n  scrollToEnd: 'Scroll til slut',\n  scrollToStart: 'Scroll til start',\n  selectAColorFromTheScreen: 'Vælg en farve fra skærmen',\n  showPassword: 'Vis adgangskode',\n  slideNum: slide => `Slide ${slide}`,\n  toggleColorFormat: 'Skift farveformat'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/de-ch.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport baseTranslation from './de.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  ...baseTranslation,\n  $code: 'de-CH',\n  $name: 'Deutsch (Schweiz)',\n\n  close: 'Schliessen',\n  resize: 'Grösse ändern'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/de.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'de',\n  $name: 'Deutsch',\n  $dir: 'ltr',\n\n  carousel: 'Karussell',\n  clearEntry: 'Eingabe löschen',\n  close: 'Schließen',\n  copied: 'Kopiert',\n  copy: 'Kopieren',\n  currentValue: 'Aktueller Wert',\n  error: 'Fehler',\n  goToSlide: (slide, count) => `Zu Folie ${slide} von ${count} gehen`,\n  hidePassword: 'Passwort verbergen',\n  loading: 'Wird geladen',\n  nextSlide: 'Nächste Folie',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Keine Optionen ausgewählt';\n    if (num === 1) return '1 Option ausgewählt';\n    return `${num} Optionen ausgewählt`;\n  },\n  previousSlide: 'Vorherige Folie',\n  progress: 'Fortschritt',\n  remove: 'Entfernen',\n  resize: 'Größe ändern',\n  scrollToEnd: 'Zum Ende scrollen',\n  scrollToStart: 'Zum Anfang scrollen',\n  selectAColorFromTheScreen: 'Farbe vom Bildschirm auswählen',\n  showPassword: 'Passwort anzeigen',\n  slideNum: slide => `Folie ${slide}`,\n  toggleColorFormat: 'Farbformat umschalten'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/en-gb.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport baseTranslation from './en.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  ...baseTranslation,\n  $code: 'en-GB',\n  $name: 'English (United Kingdom)',\n\n  selectAColorFromTheScreen: 'Select a colour from the screen',\n  toggleColorFormat: 'Toggle colour format'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/en.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'en',\n  $name: 'English',\n  $dir: 'ltr',\n\n  carousel: 'Carousel',\n  clearEntry: 'Clear entry',\n  close: 'Close',\n  copied: 'Copied',\n  copy: 'Copy',\n  currentValue: 'Current value',\n  error: 'Error',\n  goToSlide: (slide, count) => `Go to slide ${slide} of ${count}`,\n  hidePassword: 'Hide password',\n  loading: 'Loading',\n  nextSlide: 'Next slide',\n  numOptionsSelected: num => {\n    if (num === 0) return 'No options selected';\n    if (num === 1) return '1 option selected';\n    return `${num} options selected`;\n  },\n  previousSlide: 'Previous slide',\n  progress: 'Progress',\n  remove: 'Remove',\n  resize: 'Resize',\n  scrollToEnd: 'Scroll to end',\n  scrollToStart: 'Scroll to start',\n  selectAColorFromTheScreen: 'Select a color from the screen',\n  showPassword: 'Show password',\n  slideNum: slide => `Slide ${slide}`,\n  toggleColorFormat: 'Toggle color format'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/es.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'es',\n  $name: 'Español',\n  $dir: 'ltr',\n\n  carousel: 'Carrusel',\n  clearEntry: 'Borrar entrada',\n  close: 'Cerrar',\n  copied: 'Copiado',\n  copy: 'Copiar',\n  currentValue: 'Valor actual',\n  error: 'Error',\n  goToSlide: (slide, count) => `Ir a la diapositiva ${slide} de ${count}`,\n  hidePassword: 'Ocultar contraseña',\n  loading: 'Cargando',\n  nextSlide: 'Siguiente diapositiva',\n  numOptionsSelected: num => {\n    if (num === 0) return 'No hay opciones seleccionadas';\n    if (num === 1) return '1 opción seleccionada';\n    return `${num} opción seleccionada`;\n  },\n  previousSlide: 'Diapositiva anterior',\n  progress: 'Progreso',\n  remove: 'Eliminar',\n  resize: 'Cambiar el tamaño',\n  scrollToEnd: 'Desplazarse hasta el final',\n  scrollToStart: 'Desplazarse al inicio',\n  selectAColorFromTheScreen: 'Seleccione un color de la pantalla',\n  showPassword: 'Mostrar contraseña',\n  slideNum: slide => `Diapositiva ${slide}`,\n  toggleColorFormat: 'Alternar formato de color'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/fa.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'fa',\n  $name: 'فارسی',\n  $dir: 'rtl',\n\n  carousel: 'چرخ فلک',\n  clearEntry: 'پاک کردن ورودی',\n  close: 'بستن',\n  copied: 'کپی شد',\n  copy: 'رونوشت',\n  currentValue: 'مقدار فعلی',\n  error: 'خطا',\n  goToSlide: (slide, count) => `رفتن به اسلاید ${slide} از ${count}`,\n  hidePassword: 'پنهان کردن رمز',\n  loading: 'بارگذاری',\n  nextSlide: 'اسلاید بعدی',\n  numOptionsSelected: num => {\n    if (num === 0) return 'هیچ گزینه ای انتخاب نشده است';\n    if (num === 1) return '1 گزینه انتخاب شده است';\n    return `${num} گزینه انتخاب شده است`;\n  },\n  previousSlide: 'اسلاید قبلی',\n  progress: 'پیشرفت',\n  remove: 'حذف',\n  resize: 'تغییر اندازه',\n  scrollToEnd: 'پیمایش به انتها',\n  scrollToStart: 'پیمایش به ابتدا',\n  selectAColorFromTheScreen: 'انتخاب یک رنگ از صفحه نمایش',\n  showPassword: 'نمایش رمز',\n  slideNum: slide => `اسلاید ${slide}`,\n  toggleColorFormat: 'تغییر قالب رنگ'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/fi.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'fi',\n  $name: 'Suomi',\n  $dir: 'ltr',\n\n  carousel: 'Karuselli',\n  clearEntry: 'Poista merkintä',\n  close: 'Sulje',\n  copied: 'Kopioitu',\n  copy: 'Kopioi',\n  currentValue: 'Nykyinen arvo',\n  error: 'Virhe',\n  goToSlide: (slide, count) => `Siirry diaan ${slide} / ${count}`,\n  hidePassword: 'Piilota salasana',\n  loading: 'Ladataan',\n  nextSlide: 'Seuraava dia',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Ei valittuja vaihtoehtoja';\n    if (num === 1) return 'Yksi vaihtoehto valittu';\n    return `${num} vaihtoehtoa valittu`;\n  },\n  previousSlide: 'Edellinen dia',\n  progress: 'Edistyminen',\n  remove: 'Poista',\n  resize: 'Muuta kokoa',\n  scrollToEnd: 'Vieritä loppuun',\n  scrollToStart: 'Vieritä alkuun',\n  selectAColorFromTheScreen: 'Valitse väri näytöltä',\n  showPassword: 'Näytä salasana',\n  slideNum: slide => `Dia ${slide}`,\n  toggleColorFormat: 'Vaihda väriformaattia'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/fr.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'fr',\n  $name: 'Français',\n  $dir: 'ltr',\n\n  carousel: 'Carrousel',\n  clearEntry: `Effacer l'entrée`,\n  close: 'Fermer',\n  copied: 'Copié',\n  copy: 'Copier',\n  currentValue: 'Valeur actuelle',\n  error: 'Erreur',\n  goToSlide: (slide, count) => `Aller à la diapositive ${slide} de ${count}`,\n  hidePassword: 'Masquer le mot de passe',\n  loading: 'Chargement',\n  nextSlide: 'Diapositive suivante',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Aucune option sélectionnée';\n    if (num === 1) return '1 option sélectionnée';\n    return `${num} options sélectionnées`;\n  },\n  previousSlide: 'Diapositive précédente',\n  progress: 'Progrès',\n  remove: 'Retirer',\n  resize: 'Redimensionner',\n  scrollToEnd: `Faire défiler jusqu'à la fin`,\n  scrollToStart: `Faire défiler jusqu'au début`,\n  selectAColorFromTheScreen: `Sélectionnez une couleur à l'écran`,\n  showPassword: 'Montrer le mot de passe',\n  slideNum: slide => `Diapositive ${slide}`,\n  toggleColorFormat: 'Changer le format de couleur'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/he.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'he',\n  $name: 'עברית',\n  $dir: 'rtl',\n\n  carousel: 'קרוסלה',\n  clearEntry: 'נקה קלט',\n  close: 'סגור',\n  copied: 'מוּעֲתָק',\n  copy: 'העתק',\n  currentValue: 'ערך נוכחי',\n  error: 'שְׁגִיאָה',\n  goToSlide: (slide, count) => `עבור לשקופית ${slide} של ${count}`,\n  hidePassword: 'הסתר סיסמא',\n  loading: 'טוען',\n  nextSlide: 'Next slide',\n  numOptionsSelected: num => {\n    if (num === 0) return 'לא נבחרו אפשרויות';\n    if (num === 1) return 'נבחרה אפשרות אחת';\n    return `נבחרו ${num} אפשרויות`;\n  },\n  previousSlide: 'Previous slide',\n  progress: 'התקדמות',\n  remove: 'לְהַסִיר',\n  resize: 'שנה גודל',\n  scrollToEnd: 'גלול עד הסוף',\n  scrollToStart: 'גלול להתחלה',\n  selectAColorFromTheScreen: 'בחור צבע מהמסך',\n  showPassword: 'הראה סיסמה',\n  slideNum: slide => `שקופית ${slide}`,\n  toggleColorFormat: 'החלף פורמט צבע'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/hr.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'hr',\n  $name: 'Hrvatski',\n  $dir: 'ltr',\n\n  carousel: 'Vrtuljak',\n  clearEntry: 'Očisti unos',\n  close: 'Zatvori',\n  copied: 'Kopirano',\n  copy: 'Kopiraj',\n  currentValue: 'Trenutna vrijednost',\n  error: 'Greška',\n  goToSlide: (slide, count) => `Idi na slajd ${slide} od ${count}`,\n  hidePassword: 'Sakrij lozinku',\n  loading: 'Učitavanje',\n  nextSlide: 'Sljedeći slajd',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nije odabrana nijedna opcija';\n    if (num === 1) return '1 opcija je odabrana';\n    return `${num} odabranih opcija`;\n  },\n  previousSlide: 'Prethodni slajd',\n  progress: 'Napredak',\n  remove: 'Makni',\n  resize: 'Promijeni veličinu',\n  scrollToEnd: 'Skrolaj do kraja',\n  scrollToStart: 'Skrolaj na početak',\n  selectAColorFromTheScreen: 'Odaberi boju sa ekrana',\n  showPassword: 'Pokaži lozinku',\n  slideNum: slide => `Slajd ${slide}`,\n  toggleColorFormat: 'Zamijeni format boje'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/hu.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'hu',\n  $name: 'Magyar',\n  $dir: 'ltr',\n\n  carousel: 'Körhinta',\n  clearEntry: 'Bejegyzés törlése',\n  close: 'Bezárás',\n  copied: 'Másolva',\n  copy: 'Másolás',\n  currentValue: 'Aktuális érték',\n  error: 'Hiba',\n  goToSlide: (slide, count) => `Ugrás a ${count}/${slide}. diára`,\n  hidePassword: 'Jelszó elrejtése',\n  loading: 'Betöltés',\n  nextSlide: 'Következő dia',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nincsenek kiválasztva opciók';\n    if (num === 1) return '1 lehetőség kiválasztva';\n    return `${num} lehetőség kiválasztva`;\n  },\n  previousSlide: 'Előző dia',\n  progress: 'Folyamat',\n  remove: 'Eltávolítás',\n  resize: 'Átméretezés',\n  scrollToEnd: 'Görgessen a végére',\n  scrollToStart: 'Görgessen az elejére',\n  selectAColorFromTheScreen: 'Szín választása a képernyőről',\n  showPassword: 'Jelszó megjelenítése',\n  slideNum: slide => `${slide}. dia`,\n  toggleColorFormat: 'Színformátum változtatása'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/id.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'id',\n  $name: 'Bahasa Indonesia',\n  $dir: 'ltr',\n\n  carousel: 'Karousel',\n  clearEntry: 'Hapus entri',\n  close: 'Tutup',\n  copied: 'Disalin',\n  copy: 'Salin',\n  currentValue: 'Nilai saat ini',\n  error: 'Kesalahan',\n  goToSlide: (slide, count) => `Pergi ke slide ${slide} dari ${count}`,\n  hidePassword: 'Sembunyikan sandi',\n  loading: 'Memuat',\n  nextSlide: 'Slide berikutnya',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Tidak ada opsi yang dipilih';\n    if (num === 1) return '1 opsi yang dipilih';\n    return `${num} opsi yang dipilih`;\n  },\n  previousSlide: 'Slide sebelumnya',\n  progress: 'Kemajuan',\n  remove: 'Hapus',\n  resize: 'Ubah ukuran',\n  scrollToEnd: 'Gulir ke akhir',\n  scrollToStart: 'Gulir ke awal',\n  selectAColorFromTheScreen: 'Pilih warna dari layar',\n  showPassword: 'Tampilkan sandi',\n  slideNum: slide => `Slide ${slide}`,\n  toggleColorFormat: 'Beralih format warna'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/it.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'it',\n  $name: 'Italian',\n  $dir: 'ltr',\n\n  carousel: 'Carosello',\n  clearEntry: 'Cancella inserimento',\n  close: 'Chiudi',\n  copied: 'Copiato',\n  copy: 'Copia',\n  currentValue: 'Valore attuale',\n  error: 'Errore',\n  goToSlide: (slide, count) => `Vai alla diapositiva ${slide} di ${count}`,\n  hidePassword: 'Nascondi password',\n  loading: 'In caricamento',\n  nextSlide: 'Prossima diapositiva',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nessuna opzione selezionata';\n    if (num === 1) return '1 opzione selezionata';\n    return `${num} opzioni selezionate`;\n  },\n  previousSlide: 'Diapositiva precedente',\n  progress: 'Avanzamento',\n  remove: 'Rimuovi',\n  resize: 'Ridimensiona',\n  scrollToEnd: 'Scorri alla fine',\n  scrollToStart: \"Scorri all'inizio\",\n  selectAColorFromTheScreen: 'Seleziona un colore dalla schermo',\n  showPassword: 'Mostra password',\n  slideNum: slide => `Diapositiva ${slide}`,\n  toggleColorFormat: 'Cambia formato colore'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/ja.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'ja',\n  $name: '日本語',\n  $dir: 'ltr',\n\n  carousel: 'カルーセル',\n  clearEntry: 'クリア',\n  close: '閉じる',\n  copied: 'コピーしました',\n  copy: 'コピー',\n  currentValue: '現在の値',\n  error: 'エラー',\n  goToSlide: (slide, count) => `${count} 枚中 ${slide} 枚のスライドに移動`,\n  hidePassword: 'パスワードを隠す',\n  loading: '読み込み中',\n  nextSlide: '次のスライド',\n  numOptionsSelected: num => {\n    if (num === 0) return '項目が選択されていません';\n    return `${num} 個の項目が選択されました`;\n  },\n  previousSlide: '前のスライド',\n  progress: '進行',\n  remove: '削除',\n  resize: 'サイズ変更',\n  scrollToEnd: '最後にスクロールする',\n  scrollToStart: '最初にスクロールする',\n  selectAColorFromTheScreen: '画面から色を選択してください',\n  showPassword: 'パスワードを表示',\n  slideNum: slide => `スライド ${slide}`,\n  toggleColorFormat: '色のフォーマットを切り替える'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/nb.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'nb',\n  $name: 'Norwegian Bokmål',\n  $dir: 'ltr',\n\n  carousel: 'Karusell',\n  clearEntry: 'Tøm felt',\n  close: 'Lukk',\n  copied: 'Kopiert',\n  copy: 'Kopier',\n  currentValue: 'Nåværende verdi',\n  error: 'Feil',\n  goToSlide: (slide, count) => `Gå til visning ${slide} av ${count}`,\n  hidePassword: 'Skjul passord',\n  loading: 'Laster',\n  nextSlide: 'Neste visning',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Ingen alternativer valgt';\n    if (num === 1) return 'Ett alternativ valgt';\n    return `${num} alternativer valgt`;\n  },\n  previousSlide: 'Forrige visning',\n  progress: 'Fremdrift',\n  remove: 'Fjern',\n  resize: 'Endre størrelse',\n  scrollToEnd: 'Rull til slutten',\n  scrollToStart: 'Rull til starten',\n  selectAColorFromTheScreen: 'Velg en farge fra skjermen',\n  showPassword: 'Vis passord',\n  slideNum: slide => `Visning ${slide}`,\n  toggleColorFormat: 'Bytt fargeformat'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/nl.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'nl',\n  $name: 'Nederlands',\n  $dir: 'ltr',\n\n  carousel: 'Carrousel',\n  clearEntry: 'Invoer wissen',\n  close: 'Sluiten',\n  copied: 'Gekopieerd',\n  copy: 'Kopiëren',\n  currentValue: 'Huidige waarde',\n  error: 'Fout',\n  goToSlide: (slide, count) => `Ga naar slide ${slide} van ${count}`,\n  hidePassword: 'Verberg wachtwoord',\n  loading: 'Bezig met laden',\n  nextSlide: 'Volgende dia',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Geen optie geselecteerd';\n    if (num === 1) return '1 optie geselecteerd';\n    return `${num} opties geselecteerd`;\n  },\n  previousSlide: 'Vorige dia',\n  progress: 'Voortgang',\n  remove: 'Verwijderen',\n  resize: 'Formaat wijzigen',\n  scrollToEnd: 'Scroll naar einde',\n  scrollToStart: 'Scroll naar begin',\n  selectAColorFromTheScreen: 'Selecteer een kleur van het scherm',\n  showPassword: 'Laat wachtwoord zien',\n  slideNum: slide => `Schuif ${slide}`,\n  toggleColorFormat: 'Wissel kleurnotatie'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/nn.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'nn',\n  $name: 'Norwegian Nynorsk',\n  $dir: 'ltr',\n\n  carousel: 'Karusell',\n  clearEntry: 'Tøm felt',\n  close: 'Lukk',\n  copied: 'Kopiert',\n  copy: 'Kopier',\n  currentValue: 'Nåverande verdi',\n  error: 'Feil',\n  goToSlide: (slide, count) => `Gå til visning ${slide} av ${count}`,\n  hidePassword: 'Gøym passord',\n  loading: 'Lastar',\n  nextSlide: 'Neste visning',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Ingen alternativ valt';\n    if (num === 1) return 'Eitt alternativ valt';\n    return `${num} alternativ valt`;\n  },\n  previousSlide: 'Førre visning',\n  progress: 'Framdrift',\n  remove: 'Fjern',\n  resize: 'Endre storleik',\n  scrollToEnd: 'Rull til slutten',\n  scrollToStart: 'Rull til starten',\n  selectAColorFromTheScreen: 'Vel ein farge frå skjermen',\n  showPassword: 'Vis passord',\n  slideNum: slide => `Visning ${slide}`,\n  toggleColorFormat: 'Byt fargeformat'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/pl.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'pl',\n  $name: 'Polski',\n  $dir: 'ltr',\n\n  carousel: 'Karuzela',\n  clearEntry: 'Wyczyść wpis',\n  close: 'Zamknij',\n  copied: 'Skopiowane',\n  copy: 'Kopiuj',\n  currentValue: 'Aktualna wartość',\n  error: 'Błąd',\n  goToSlide: (slide, count) => `Przejdź do slajdu ${slide} z ${count}`,\n  hidePassword: 'Ukryj hasło',\n  loading: 'Ładowanie',\n  nextSlide: 'Następny slajd',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nie wybrano opcji';\n    if (num === 1) return 'Wybrano 1 opcję';\n    return `Wybrano ${num} opcje`;\n  },\n  previousSlide: 'Poprzedni slajd',\n  progress: 'Postęp',\n  remove: 'Usunąć',\n  resize: 'Zmień rozmiar',\n  scrollToEnd: 'Przewiń do końca',\n  scrollToStart: 'Przewiń do początku',\n  selectAColorFromTheScreen: 'Próbkuj z ekranu',\n  showPassword: 'Pokaż hasło',\n  slideNum: slide => `Slajd ${slide}`,\n  toggleColorFormat: 'Przełącz format'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/pt.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'pt',\n  $name: 'Português',\n  $dir: 'ltr',\n\n  carousel: 'Carrossel',\n  clearEntry: 'Limpar entrada',\n  close: 'Fechar',\n  copied: 'Copiado',\n  copy: 'Copiar',\n  currentValue: 'Valor atual',\n  error: 'Erro',\n  goToSlide: (slide, count) => `Vá para o slide ${slide} de ${count}`,\n  hidePassword: 'Esconder a senha',\n  loading: 'Carregando',\n  nextSlide: 'Próximo slide',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nenhuma opção selecionada';\n    if (num === 1) return '1 opção selecionada';\n    return `${num} opções selecionadas`;\n  },\n  previousSlide: 'Slide anterior',\n  progress: 'Progresso',\n  remove: 'Remover',\n  resize: 'Mudar o tamanho',\n  scrollToEnd: 'Rolar até o final',\n  scrollToStart: 'Rolar até o início',\n  selectAColorFromTheScreen: 'Selecionar uma cor da tela',\n  showPassword: 'Mostrar senha',\n  slideNum: slide => `Slide ${slide}`,\n  toggleColorFormat: 'Trocar o formato de cor'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/ru.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'ru',\n  $name: 'Русский',\n  $dir: 'ltr',\n\n  carousel: 'Карусель',\n  clearEntry: 'Очистить запись',\n  close: 'Закрыть',\n  copied: 'Скопировано',\n  copy: 'Скопировать',\n  currentValue: 'Текущее значение',\n  error: 'Ошибка',\n  goToSlide: (slide, count) => `Перейти к слайду ${slide} из ${count}`,\n  hidePassword: 'Скрыть пароль',\n  loading: 'Загрузка',\n  nextSlide: 'Следующий слайд',\n  numOptionsSelected: num => {\n    if (num === 0) return 'выбрано 0 вариантов';\n    if (num === 1) return 'Выбран 1 вариант';\n    return `выбрано ${num} варианта`;\n  },\n  previousSlide: 'Предыдущий слайд',\n  progress: 'Прогресс',\n  remove: 'Удалить',\n  resize: 'Изменить размер',\n  scrollToEnd: 'Пролистать до конца',\n  scrollToStart: 'Пролистать к началу',\n  selectAColorFromTheScreen: 'Выберите цвет на экране',\n  showPassword: 'Показать пароль',\n  slideNum: slide => `Слайд ${slide}`,\n  toggleColorFormat: 'Переключить цветовую модель'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/sl.ts",
    "content": "import { registerTranslation } from '@shoelace-style/localize';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'sl',\n  $name: 'Slovenski',\n  $dir: 'ltr',\n\n  carousel: 'Vrtiljak',\n  clearEntry: 'Počisti vnos',\n  close: 'Zapri',\n  copied: 'Kopirano',\n  copy: 'Kopiraj',\n  currentValue: 'Trenutna vrednost',\n  error: 'Napaka',\n  goToSlide: (slide, count) => `Pojdi na diapozitiv ${slide} od ${count}`,\n  hidePassword: 'Skrij geslo',\n  loading: 'Nalaganje',\n  nextSlide: 'Naslednji diapozitiv',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Nobena možnost ni izbrana';\n    if (num === 1) return '1 možnost izbrana';\n    if (num === 2) return '2 možnosti izbrani';\n    if (num === 3 || num === 4) return `${num} možnosti izbrane`;\n    return `${num} možnosti izbranih`;\n  },\n  previousSlide: 'Prejšnji diapozitiv',\n  progress: 'Napredek',\n  remove: 'Odstrani',\n  resize: 'Spremeni velikost',\n  scrollToEnd: 'Pomakni se na konec',\n  scrollToStart: 'Pomakni se na začetek',\n  selectAColorFromTheScreen: 'Izberite barvo z zaslona',\n  showPassword: 'Prikaži geslo',\n  slideNum: slide => `Diapozitiv ${slide}`,\n  toggleColorFormat: 'Preklopi format barve'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/sv.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'sv',\n  $name: 'Svenska',\n  $dir: 'ltr',\n\n  carousel: 'Karusell',\n  clearEntry: 'Återställ val',\n  close: 'Stäng',\n  copied: 'Kopierade',\n  copy: 'Kopiera',\n  currentValue: 'Nuvarande värde',\n  error: 'Fel',\n  goToSlide: (slide, count) => `Gå till bild ${slide} av ${count}`,\n  hidePassword: 'Dölj lösenord',\n  loading: 'Läser in',\n  nextSlide: 'Nästa bild',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Inga alternativ har valts';\n    if (num === 1) return '1 alternativ valt';\n    return `${num} alternativ valda`;\n  },\n  previousSlide: 'Föregående bild',\n  progress: 'Framsteg',\n  remove: 'Ta bort',\n  resize: 'Ändra storlek',\n  scrollToEnd: 'Skrolla till slutet',\n  scrollToStart: 'Skrolla till början',\n  selectAColorFromTheScreen: 'Välj en färg från skärmen',\n  showPassword: 'Visa lösenord',\n  slideNum: slide => `Bild ${slide}`,\n  toggleColorFormat: 'Växla färgformat'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/tr.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'tr',\n  $name: 'Türkçe',\n  $dir: 'ltr',\n\n  carousel: 'Atlıkarınca',\n  clearEntry: 'Girişi sil',\n  close: 'Kapat',\n  copied: 'Kopyalandı',\n  copy: 'Kopya',\n  currentValue: 'Mevcut değer',\n  error: 'Hata',\n  goToSlide: (slide, count) => `${count} slayttan ${slide} slayta gidin`,\n  hidePassword: 'Şifreyi sakla',\n  loading: 'Yükleme',\n  nextSlide: 'Sonraki slayt',\n  numOptionsSelected: num => {\n    if (num === 0) return 'Hiçbir seçenek seçilmedi';\n    if (num === 1) return '1 seçenek seçildi';\n    return `${num} seçenek seçildi`;\n  },\n  previousSlide: 'Bir onceki slayt',\n  progress: 'İlerleme',\n  remove: 'Kaldır',\n  resize: 'Yeniden boyutlandır',\n  scrollToEnd: 'Sona kay',\n  scrollToStart: 'Başa kay',\n  selectAColorFromTheScreen: 'Ekrandan bir renk seçin',\n  showPassword: 'Şifreyi göster',\n  slideNum: slide => `Slayt ${slide}`,\n  toggleColorFormat: 'Renk biçimini değiştir'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/uk.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'uk',\n  $name: 'Українська',\n  $dir: 'ltr',\n\n  carousel: 'Карусель',\n  clearEntry: 'Очистити поле',\n  close: 'Закрити',\n  copied: 'Скопійовано',\n  copy: 'Скопіювати',\n  currentValue: 'Поточне значення',\n  error: 'Збій',\n  goToSlide: (slide, count) => `Перейти до слайда №${slide} з ${count}`,\n  hidePassword: 'Приховати пароль',\n  loading: 'Завантаження',\n  nextSlide: 'Наступний слайд',\n  numOptionsSelected: num => {\n    const n = num % 10;\n    if (n === 0) return 'не вибрано варіантів';\n    if (n === 1) return 'вибрано 1 варіант';\n    if (n === 2 || n === 3 || n === 4) return `вибрано ${num} варіанти`;\n    return `вибрано ${num} варіантів`;\n  },\n  previousSlide: 'Попередній слайд',\n  progress: 'Поступ',\n  remove: 'Видалити',\n  resize: 'Змінити розмір',\n  scrollToEnd: 'Прокрутити в кінець',\n  scrollToStart: 'Прокрутити на початок',\n  selectAColorFromTheScreen: 'Виберіть колір на екрані',\n  showPassword: 'Показати пароль',\n  slideNum: slide => `Слайд ${slide}`,\n  toggleColorFormat: 'Переключити кольорову модель'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/zh-cn.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'zh-cn',\n  $name: '简体中文',\n  $dir: 'ltr',\n\n  carousel: '跑马灯',\n  clearEntry: '清空',\n  close: '关闭',\n  copied: '已复制',\n  copy: '复制',\n  currentValue: '当前值',\n  error: '错误',\n  goToSlide: (slide, count) => `转到第 ${slide} 张幻灯片，共 ${count} 张`,\n  hidePassword: '隐藏密码',\n  loading: '加载中',\n  nextSlide: '下一张幻灯片',\n  numOptionsSelected: num => {\n    if (num === 0) return '未选择任何项目';\n    if (num === 1) return '已选择 1 个项目';\n    return `${num} 选择项目`;\n  },\n  previousSlide: '上一张幻灯片',\n  progress: '进度',\n  remove: '删除',\n  resize: '调整大小',\n  scrollToEnd: '滚动至页尾',\n  scrollToStart: '滚动至页首',\n  selectAColorFromTheScreen: '从屏幕中选择一种颜色',\n  showPassword: '显示密码',\n  slideNum: slide => `幻灯片 ${slide}`,\n  toggleColorFormat: '切换颜色模式'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/translations/zh-tw.ts",
    "content": "import { registerTranslation } from '../utilities/localize.js';\nimport type { Translation } from '../utilities/localize.js';\n\nconst translation: Translation = {\n  $code: 'zh-tw',\n  $name: '正體中文',\n  $dir: 'ltr',\n\n  carousel: '幻燈片',\n  clearEntry: '清空',\n  close: '關閉',\n  copied: '已複製',\n  copy: '複製',\n  currentValue: '當前值',\n  error: '錯誤',\n  goToSlide: (slide, count) => `轉到第 ${slide} 張幻燈片，共 ${count} 張`,\n  hidePassword: '隱藏密碼',\n  loading: '載入中',\n  nextSlide: '下一張幻燈片',\n  numOptionsSelected: num => {\n    if (num === 0) return '未選擇任何項目';\n    if (num === 1) return '已選擇 1 個項目';\n    return `${num} 選擇項目`;\n  },\n  previousSlide: '上一張幻燈片',\n  progress: '進度',\n  remove: '移除',\n  resize: '調整大小',\n  scrollToEnd: '捲至頁尾',\n  scrollToStart: '捲至頁首',\n  selectAColorFromTheScreen: '從螢幕中選擇一種顏色',\n  showPassword: '顯示密碼',\n  slideNum: slide => `幻燈片 ${slide}`,\n  toggleColorFormat: '切換顏色格式'\n};\n\nregisterTranslation(translation);\n\nexport default translation;\n"
  },
  {
    "path": "src/utilities/animation-registry.ts",
    "content": "export interface ElementAnimation {\n  keyframes: Keyframe[];\n  rtlKeyframes?: Keyframe[];\n  options?: KeyframeAnimationOptions;\n}\n\nexport interface ElementAnimationMap {\n  [animationName: string]: ElementAnimation;\n}\n\nexport interface GetAnimationOptions {\n  /**\n   * The component's directionality. When set to \"rtl\", `rtlKeyframes` will be preferred over `keyframes` where\n   * available using getAnimation().\n   */\n  dir: string;\n}\n\nconst defaultAnimationRegistry = new Map<string, ElementAnimation>();\nconst customAnimationRegistry = new WeakMap<Element, ElementAnimationMap>();\n\nfunction ensureAnimation(animation: ElementAnimation | null) {\n  return animation ?? { keyframes: [], options: { duration: 0 } };\n}\n\n//\n// Given an ElementAnimation, this function returns a new ElementAnimation where the keyframes property reflects either\n// keyframes or rtlKeyframes depending on the specified directionality.\n//\nfunction getLogicalAnimation(animation: ElementAnimation, dir: string) {\n  if (dir.toLowerCase() === 'rtl') {\n    return {\n      keyframes: animation.rtlKeyframes || animation.keyframes,\n      options: animation.options\n    };\n  }\n\n  return animation;\n}\n\n/**\n * Sets a default animation. Components should use the `name.animation` for primary animations and `name.part.animation`\n * for secondary animations, e.g. `dialog.show` and `dialog.overlay.show`. For modifiers, use `drawer.showTop`.\n */\nexport function setDefaultAnimation(animationName: string, animation: ElementAnimation | null) {\n  defaultAnimationRegistry.set(animationName, ensureAnimation(animation));\n}\n\n/** Sets a custom animation for the specified element. */\nexport function setAnimation(el: Element, animationName: string, animation: ElementAnimation | null) {\n  customAnimationRegistry.set(el, { ...customAnimationRegistry.get(el), [animationName]: ensureAnimation(animation) });\n}\n\n/** Gets an element's animation. Falls back to the default if no animation is found. */\nexport function getAnimation(el: Element, animationName: string, options: GetAnimationOptions) {\n  const customAnimation = customAnimationRegistry.get(el);\n\n  // Check for a custom animation\n  if (customAnimation?.[animationName]) {\n    return getLogicalAnimation(customAnimation[animationName], options.dir);\n  }\n\n  // Check for a default animation\n  const defaultAnimation = defaultAnimationRegistry.get(animationName);\n  if (defaultAnimation) {\n    return getLogicalAnimation(defaultAnimation, options.dir);\n  }\n\n  // Fall back to an empty animation\n  return {\n    keyframes: [],\n    options: { duration: 0 }\n  };\n}\n"
  },
  {
    "path": "src/utilities/animation.ts",
    "content": "export { getAnimationNames, getEasingNames } from '../components/animation/animations.js';\n"
  },
  {
    "path": "src/utilities/base-path.ts",
    "content": "let basePath = '';\n\n/** Sets the library's base path to the specified directory. */\nexport function setBasePath(path: string) {\n  basePath = path;\n}\n\n/**\n * Gets the library's base path.\n *\n * The base path is used to load assets such as icons and images, so it needs to be set for components to work properly.\n * By default, this script will look for a script ending in shoelace.js or shoelace-autoloader.js and set the base path\n * to the directory that contains that file. To override this behavior, you can add the data-shoelace attribute to any\n * script on the page (it probably makes the most sense to attach it to the Shoelace script, but it could also be on a\n * bundle). The value can be a local folder or it can point to a CORS-enabled endpoint such as a CDN.\n *\n *   <script src=\"bundle.js\" data-shoelace=\"/custom/base/path\"></script>\n *\n * Alternatively, you can set the base path manually using the exported setBasePath() function.\n *\n * @param subpath - An optional path to append to the base path.\n */\nexport function getBasePath(subpath = '') {\n  if (!basePath) {\n    const scripts = [...document.getElementsByTagName('script')] as HTMLScriptElement[];\n    const configScript = scripts.find(script => script.hasAttribute('data-shoelace'));\n\n    if (configScript) {\n      // Use the data-shoelace attribute\n      setBasePath(configScript.getAttribute('data-shoelace')!);\n    } else {\n      const fallbackScript = scripts.find(s => {\n        return /shoelace(\\.min)?\\.js($|\\?)/.test(s.src) || /shoelace-autoloader(\\.min)?\\.js($|\\?)/.test(s.src);\n      });\n      let path = '';\n\n      if (fallbackScript) {\n        path = fallbackScript.getAttribute('src')!;\n      }\n\n      setBasePath(path.split('/').slice(0, -1).join('/'));\n    }\n  }\n\n  // Return the base path without a trailing slash. If one exists, append the subpath separated by a slash.\n  return basePath.replace(/\\/$/, '') + (subpath ? `/${subpath.replace(/^\\//, '')}` : ``);\n}\n"
  },
  {
    "path": "src/utilities/form.ts",
    "content": "import { formCollections } from '../internal/form.js';\n\n/**\n * Serializes a form and returns a plain object. If a form control with the same name appears more than once, the\n * property will be converted to an array.\n */\nexport function serialize(form: HTMLFormElement) {\n  const formData = new FormData(form);\n  const object: Record<string, unknown> = {};\n\n  formData.forEach((value, key) => {\n    if (Reflect.has(object, key)) {\n      const entry = object[key];\n      if (Array.isArray(entry)) {\n        entry.push(value);\n      } else {\n        object[key] = [object[key], value];\n      }\n    } else {\n      object[key] = value;\n    }\n  });\n\n  return object;\n}\n\n/**\n * Returns all form controls that are associated with the specified form. Includes both native and Shoelace form\n * controls. Use this function in lieu of the `HTMLFormElement.elements` property, which doesn't recognize Shoelace\n * form controls.\n */\nexport function getFormControls(form: HTMLFormElement) {\n  const rootNode = form.getRootNode() as Document | ShadowRoot;\n  const allNodes = [...rootNode.querySelectorAll('*')];\n  const formControls = [...form.elements];\n  const collection = formCollections.get(form);\n  const shoelaceFormControls = collection ? Array.from(collection) : [];\n\n  // To return form controls in the right order, we sort by DOM index\n  return [...formControls, ...shoelaceFormControls].sort((a: Element, b: Element) => {\n    if (allNodes.indexOf(a) < allNodes.indexOf(b)) return -1;\n    if (allNodes.indexOf(a) > allNodes.indexOf(b)) return 1;\n    return 0;\n  });\n}\n"
  },
  {
    "path": "src/utilities/icon-library.ts",
    "content": "export { registerIconLibrary, unregisterIconLibrary } from '../components/icon/library.js';\n"
  },
  {
    "path": "src/utilities/localize.ts",
    "content": "import { LocalizeController as DefaultLocalizationController, registerTranslation } from '@shoelace-style/localize';\nimport en from '../translations/en.js'; // Register English as the default/fallback language\nimport type { Translation as DefaultTranslation } from '@shoelace-style/localize';\n\n// Extend the controller and apply our own translation interface for better typings\nexport class LocalizeController extends DefaultLocalizationController<Translation> {\n  // Technicallly '../translations/en.js' is supposed to work via side-effects. However, by some mystery sometimes the\n  // translations don't get bundled as expected resulting in `no translation found` errors.\n  // This is basically some extra assurance that our translations get registered prior to our localizer connecting in a component\n  // and we don't rely on implicit import ordering.\n  static {\n    registerTranslation(en);\n  }\n}\n\n// Export functions from the localize lib so we have one central place to import them from\nexport { registerTranslation } from '@shoelace-style/localize';\n\nexport interface Translation extends DefaultTranslation {\n  $code: string; // e.g. en, en-GB\n  $name: string; // e.g. English, Español\n  $dir: 'ltr' | 'rtl';\n\n  carousel: string;\n  clearEntry: string;\n  close: string;\n  copied: string;\n  copy: string;\n  currentValue: string;\n  error: string;\n  goToSlide: (slide: number, count: number) => string;\n  hidePassword: string;\n  loading: string;\n  nextSlide: string;\n  numOptionsSelected: (num: number) => string;\n  previousSlide: string;\n  progress: string;\n  remove: string;\n  resize: string;\n  scrollToEnd: string;\n  scrollToStart: string;\n  selectAColorFromTheScreen: string;\n  showPassword: string;\n  slideNum: (slide: number) => string;\n  toggleColorFormat: string;\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "/* This is the \"dev\" TS Config which includes typechecking for test. This is used by ESLint to provide TypeScript errors for test files */\n{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig.json to read more about this file */\n    \"target\": \"es2017\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"lib\": [\n      \"dom\",\n      \"dom.Iterable\",\n      \"es2020\"\n    ],\n    \"emitDeclarationOnly\": true,\n    \"declaration\": true,\n    \"rootDir\": \".\",\n    \"strict\": true,\n    \"strictPropertyInitialization\": false,\n    \"strictFunctionTypes\": false,\n    \"noImplicitThis\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"esModuleInterop\": true,\n    \"experimentalDecorators\": true,\n    \"useDefineForClassFields\": false, /* See https://lit.dev/docs/components/properties/#avoiding-issues-with-class-fields */\n    \"removeComments\": false,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"useUnknownInCatchVariables\": true,\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"types\": [\n      \"mocha\",\n      \"user-agent-data-types\"\n    ]\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "tsconfig.prod.json",
    "content": "{\n  \"extends\": \"./tsconfig\",\n  \"compilerOptions\": {\n    \"rootDir\": \"./src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"src/**/*.test.ts\"]\n}\n"
  },
  {
    "path": "web-test-runner.config.js",
    "content": "import { esbuildPlugin } from '@web/dev-server-esbuild';\nimport { globbySync } from 'globby';\nimport { playwrightLauncher } from '@web/test-runner-playwright';\n\nexport default {\n  rootDir: '.',\n  files: 'src/**/*.test.ts', // \"default\" group\n  concurrentBrowsers: 3,\n  nodeResolve: {\n    exportConditions: ['production', 'default']\n  },\n  testFramework: {\n    config: {\n      timeout: 3000,\n      retries: 1\n    }\n  },\n  plugins: [\n    esbuildPlugin({\n      ts: true,\n      target: 'es2020'\n    })\n  ],\n  browsers: [\n    playwrightLauncher({ product: 'chromium' }),\n    // Firefox started failing randomly so we're temporarily disabling it here. This could be a rogue test, not really\n    // sure what's happening.\n    // playwrightLauncher({ product: 'firefox' }),\n    playwrightLauncher({ product: 'webkit' })\n  ],\n  testRunnerHtml: testFramework => `\n    <html lang=\"en-US\">\n      <head></head>\n      <body>\n        <link rel=\"stylesheet\" href=\"dist/themes/light.css\">\n        <script>\n          window.process = {env: { NODE_ENV: \"production\" }}\n        </script>\n        <script type=\"module\" src=\"${testFramework}\"></script>\n      </body>\n    </html>\n  `,\n  // Create a named group for every test file to enable running single tests. If a test file is `split-panel.test.ts`\n  // then you can run `npm run test -- --group split-panel` to run only that component's tests.\n  groups: globbySync('src/**/*.test.ts').map(path => {\n    const groupName = path.match(/^.*\\/(?<fileName>.*)\\.test\\.ts/).groups.fileName;\n    return {\n      name: groupName,\n      files: path\n    };\n  })\n};\n"
  }
]